diff --git a/.gitignore b/.gitignore index e5b54a5fa..4922ef6df 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,9 @@ node_modules/ !tests/fixtures/npm/node_modules/ !tests/corpus/*/node_modules/ !tests/fixtures/npm-static/node_modules/ +!tests/coverage-fixtures/wasm/node_modules/ !tests/fixtures/npm-static/nested/inner/node_modules/ +!tests/fixtures/npm-static/node_modules/*/node_modules/ !tests/fixtures/npm/store/*/node_modules/ !tests/fixtures/npm/cases/*/node_modules/ !tests/library-mode/*/node_modules/ @@ -32,6 +34,7 @@ output/ !tests/fixtures/npm/cases/workspace-copied/members/*/dist/ !tests/fixtures/npm/cases/workspace-copied/node_modules/*/dist/ !tests/fixtures/provenance/node_modules/*/dist/ +!tests/fixtures/npm-static/node_modules/*/dist/ .scriptc/ *.log .DS_Store diff --git a/.oxlintrc.jsonc b/.oxlintrc.jsonc index 52cbf5026..0c5cad25f 100644 --- a/.oxlintrc.jsonc +++ b/.oxlintrc.jsonc @@ -270,6 +270,7 @@ "packages/compiler/src/backend/llvm/**", "packages/compiler/src/backend/loop-polls.test.ts", "packages/compiler/src/backend/may-throw.test.ts", + "packages/compiler/src/backend/stack-observations.test.ts", "packages/compiler/src/ir/**", "packages/compiler/src/library/*.test.ts", "packages/compiler/test/**", diff --git a/AGENTS.md b/AGENTS.md index 4d66cb42f..b94c90067 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,12 @@ Corpus programs are differential tests against Node: every program runs under No Before opening a PR that adds or changes corpus, diagnostic, or frontend fixtures, run `pnpm test:ts7 --baselines-only`. This fast completeness check uses the same fixture discovery as the parity suite; it does not type-check or replace behavioral parity. Generate missing preflight/order baselines, review their diagnostics and module order, and run the affected `order-parity.test.ts` batches before pushing. Keep existing baseline entries unchanged unless the corresponding behavior intentionally changes. The full `pnpm test:ts7` command also checks completeness before starting its sweep. +A change that must not cost good code anything, such as a compatibility fix that replaces a refusal, proves it with the zero-cost gate. The gate compiles the benchmark workloads, the self-hosted compiler, and the TypeScript corpus with two built checkouts. It requires byte-identical IR, LLVM, and release executables, and compile time within tolerance. `--allow=` names the change's pattern signature: programs whose sources match may differ, and the PR lists them. Any other difference fails the gate, which names the first differing function. The gate also enforces the two-tier runtime separation: no executable may gain symbols from `DYNAMIC_TIER_PREFIXES` in `scripts/zero-cost-compare.mjs`, and fully typed benchmarks may link none; extend that list as dynamic-tier units land. `--quick` checks only the benchmarks; `--cachegrind` adds per-function instruction counts for executables that differ. Run it in a Sandbox with both checkouts prepared: + +```bash +node scripts/zero-cost-gate.mjs --baseline=/vercel/sandbox/prev --candidate=. --allow='\barguments\b' +``` + Test location follows scope: - Co-locate white-box unit tests with implementation files under diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index a6f75c6d3..c4fdf7876 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -119,7 +119,7 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
On Linux, when the program calls process.exit(), hand the release of its memory to a helper process so the parent sees the exit sooner. The kernel otherwise frees the whole address space before the exit is reported, which takes tens of milliseconds per gigabyte. Opt in only where the trade-offs are acceptable: for up to about 100 ms after the exit, the helper keeps the executable mapped (overwriting the binary in place fails with "text file busy"), its memory stays charged to the cgroup, and the teardown CPU time no longer appears in the parent's resource usage. Valid only for executable builds; it has no effect on other platforms.
--npm-static <pkg[,pkg…]|auto>
-
Name npm packages to compile even if they don't ship their own type declarations (repeatable). auto uses the default automatic selection. Packages that can't compile produce diagnostics. See npm Dependencies.
+
Name npm packages to compile even if they don't ship their own type declarations (repeatable). A trailing ..., as in fs-extra..., also names every package that package depends on. auto uses the default automatic selection. Packages that can't compile produce diagnostics. See npm Dependencies.
--provenance-sources
Experimental. Compile npm dependencies from the source at the commit their provenance attestation names. Packages without a usable attestation compile from their installed files.
@@ -127,6 +127,15 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
--external-types <specifier=file.d.ts>
Coverage only. Map an exact bare module specifier to a local declaration file supplied by an embedder. Repeat the option for multiple modules. The declaration supplies checker types so application coverage can continue; runtime imports and values remain explicit SC1010 blockers. Relative paths resolve from the current working directory. Accepted files end in .d.ts, .d.mts, or .d.cts.
+
--tier <auto|static-only>
+
The tier policy for code that has no static lowering. auto is the default. static-only guarantees that the program contains no dynamic-tier demotions: every such construct stays a compile error in TypeScript and a deferred runtime error in JavaScript, which is how both modes behave today. Use it in CI to keep a program fully static. Library mode is always static-only. See Dynamic tier.
+ +
--explain-dynamic[=<name>]
+
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.
+
--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/docs/coverage.mdx b/docs/content/docs/coverage.mdx index 2d26e684b..805d7f612 100644 --- a/docs/content/docs/coverage.mdx +++ b/docs/content/docs/coverage.mdx @@ -36,6 +36,7 @@ When a program has unsupported operations, the report adds a group for each root statements analyzedExecutable statements in application code, excluding dependency implementation code. compile staticallyStatements that compile to native instructions. + in dynamic tierOf those, the statements in functions that operate on values typed only at runtime, such as any values and untyped JavaScript. Shown when there are any. See Dynamic tier. blockersUnsupported operations with counts, diagnostic codes, reasons, locations, and hints. in unreached codeBlockers in code the entry never reaches. A build never compiles that code, so these cannot fail it. differs from NodeSites that compile but can behave differently from Node.js. See Node divergences. @@ -51,6 +52,16 @@ If a function's signature can't compile, the statements in its body count as not + + +## Dynamic tier + +Code that operates on values whose types are known only at runtime, such as `any` values, the result of an untyped `JSON.parse`, or untyped JavaScript, compiles into dynamic-tier functions. They are native code too, but they check types and look up properties at runtime, so they are slower than code with static types. The report's `in dynamic tier` line counts their statements. + +`--explain-dynamic` lists those functions and the operation that makes each one dynamic. It also lists the units an unsupported construct would move to the dynamic tier: the binding, field, class, function, or module that has no static representation for the construct, with the diagnostic behind each one. These are the constructs the dynamic tier is planned to compile; today they remain blockers in TypeScript and deferred runtime errors in JavaScript. Add a name, such as `--explain-dynamic=Point`, to show only the matching units. With `--print=diagnostics`, the document gains a `tiers` object with the same information. + +`--tier=static-only` keeps every unsupported construct a blocker instead of moving it to the dynamic tier; code that already operates on runtime-typed values compiles as before. Today `auto`, the default, behaves the same way. + ## Diagnostic codes Coverage and `scriptc build` report the same diagnostic codes. Common ones include: @@ -160,7 +171,7 @@ For editors, CI annotations, and other tools, `scriptc coverage --print=diagnost stats - Coverage only, absent after a preflight failure: the report's statement counts. statementsDynamic is always 0 and remains for schema compatibility. + Coverage only, absent after a preflight failure: the report's statement counts. statementsStatic counts every statement that compiles to native code; statementsDynamic is the part of those in dynamic-tier functions. category diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 5ecc144c6..3c6958841 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -46,6 +46,8 @@ Your callback and the commander implementation both compile to native code. No e scriptc finds packages in `node_modules` with Node.js's resolution rules, including the export conditions in `package.json`, and compiles their dependencies too. TypeScript workspace packages compile as part of your program, including `.js` import specifiers that resolve to `.ts` sources. +Type-only imports follow Node.js's type stripping. `import type` and `export type … from` statements are erased, so a package or module named only by them supplies types and isn't compiled or loaded; it can even be a package with no JavaScript, such as `type-fest`. A clause whose specifiers are all marked `type`, such as `import { type Options } from "./config.ts"`, still loads its module, as it does under Node.js. + ## Which packages compile scriptc compiles a package automatically when it: @@ -53,7 +55,7 @@ scriptc compiles a package automatically when it: - ships its own type declarations, and - ships readable, unminified JavaScript. -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 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. @@ -67,7 +69,7 @@ Some packages rely on separately installed declarations, such as an `@types/` pa $ 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. `--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. 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. ## CommonJS packages diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index 544bd2b2d..ae166836f 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -76,6 +76,8 @@ title: "Limitations" ../limitations/builtin-apis/legacy-url-api.mdx +../limitations/builtin-apis/deprecated-modules.mdx + ../limitations/builtin-apis/filesystem-flags.mdx ../limitations/builtin-apis/copy.mdx @@ -118,6 +120,8 @@ title: "Limitations" ../limitations/builtin-apis/synchronous-iteration.mdx +../limitations/builtin-apis/iterator-helpers.mdx + ../limitations/builtin-apis/regular-expressions.mdx ../limitations/builtin-apis/object-freezing.mdx @@ -236,6 +240,10 @@ title: "Limitations" ../limitations/native-addons.mdx +## WebAssembly modules + +../limitations/webassembly-modules.mdx + ## WASI target ../limitations/wasi-target.mdx diff --git a/docs/content/limitations/builtin-apis/crypto.mdx b/docs/content/limitations/builtin-apis/crypto.mdx index 586e0b1fd..988228aff 100644 --- a/docs/content/limitations/builtin-apis/crypto.mdx +++ b/docs/content/limitations/builtin-apis/crypto.mdx @@ -1 +1 @@ -- **Crypto:** Hashing, HMAC, PBKDF2, and HKDF support every digest `crypto.getHashes()` lists in Node 24, with OpenSSL's aliases and object identifiers. Hash and Hmac accept every Buffer encoding and any TypedArray or DataView, `createHash` and `hash.copy` take `outputLength`, and `crypto.hash` takes its options object; HMAC, PBKDF2, and HKDF reject the SHAKE and KMAC digests with Node's errors. Hash and Hmac values are also Transforms: they can be written, piped into, used as `pipeline()` stages, read, and listened to, and they report Node's errors after the stream flushes. `copy()` of a Hash whose stream flushed, and stream use of a value after its stream closed, throw a coded SC2020 error. `timingSafeEqual` compares any view or ArrayBuffer. Secret KeyObjects (`createSecretKey`, `generateKey` and `generateKeySync` for `hmac` and `aes`) support `type`, `symmetricKeySize`, `export()`, `equals()`, and `instanceof KeyObject`, and createHmac, hkdf, createCipheriv, and createDecipheriv accept them; asymmetric keys, `KeyObject.from()`, and `toCryptoKey()` are not available. `randomFill`, `randomFillSync` (every view kind and ArrayBuffer), `randomInt`, `randomUUID`, and `randomUUIDv7` follow Node 24.22, including callback timing; a `randomInt` callback whose error parameter is typed `Error | null` receives `null` where Node passes `undefined`. Scrypt supports its default parameters, numeric options, aliases, and memory limit. Both KDFs accept strings, Buffers, numeric typed arrays, DataViews, and fixed-length ArrayBuffers. `createCipheriv` and `createDecipheriv` support aes-128, aes-192, and aes-256 in CBC, CTR, and GCM modes and chacha20-poly1305, on native targets, through `update()`, `final()`, `setAAD()`, `setAutoPadding()`, `getAuthTag()`, and `setAuthTag()`; other ciphers, cipher streams, and wasm32-wasi builds are refused. Decrypting chacha20-poly1305 without an authentication tag fails in `final()`; Node 24.22 returns unverified plaintext. After `update()` fails, for example past the AES-GCM limit of 2^36 - 32 bytes per message, every later call on that cipher throws; Node still lets `final()` run. An output encoding that is a function or an instance of a class defining `toString` throws a coded SC2020 error. Public-key operations remain unsupported. Native derivations finish before their deferred callbacks, so computation blocks the caller. +- **Crypto:** Hashing, HMAC, PBKDF2, and HKDF support every digest `crypto.getHashes()` lists in Node 24, with OpenSSL's aliases and object identifiers. Hash and Hmac accept every Buffer encoding and any TypedArray or DataView, `createHash` and `hash.copy` take `outputLength`, and `crypto.hash` takes its options object; HMAC, PBKDF2, and HKDF reject the SHAKE and KMAC digests with Node's errors. Hash and Hmac values are also Transforms: they can be written, piped into, used as `pipeline()` stages, read, and listened to, and they report Node's errors after the stream flushes. `copy()` of a Hash whose stream flushed, and stream use of a value after its stream closed, throw a coded SC2020 error. Through `unknown` and `any` slots and untyped JavaScript, Hash and Hmac values keep their identity and support `update()`, `digest()`, and Hash's `copy()`, called or read as values; their stream and event members there throw a coded SC2020 error. `timingSafeEqual` compares any view or ArrayBuffer. Secret KeyObjects (`createSecretKey`, `generateKey` and `generateKeySync` for `hmac` and `aes`) support `type`, `symmetricKeySize`, `export()`, `equals()`, and `instanceof KeyObject`, and createHmac, hkdf, createCipheriv, and createDecipheriv accept them; asymmetric keys, `KeyObject.from()`, and `toCryptoKey()` are not available. `randomFill`, `randomFillSync` (every view kind and ArrayBuffer), `randomInt`, `randomUUID`, and `randomUUIDv7` follow Node 24.22, including callback timing; a `randomInt` callback whose error parameter is typed `Error | null` receives `null` where Node passes `undefined`. Scrypt supports its default parameters, numeric options, aliases, and memory limit. Both KDFs accept strings, Buffers, numeric typed arrays, DataViews, and fixed-length ArrayBuffers. `createCipheriv` and `createDecipheriv` support aes-128, aes-192, and aes-256 in CBC, CTR, and GCM modes and chacha20-poly1305, on native targets, through `update()`, `final()`, `setAAD()`, `setAutoPadding()`, `getAuthTag()`, and `setAuthTag()`; other ciphers, cipher streams, and wasm32-wasi builds are refused. Decrypting chacha20-poly1305 without an authentication tag fails in `final()`; Node 24.22 returns unverified plaintext. After `update()` fails, for example past the AES-GCM limit of 2^36 - 32 bytes per message, every later call on that cipher throws; Node still lets `final()` run. An output encoding that is a function or an instance of a class defining `toString` throws a coded SC2020 error. Public-key operations remain unsupported. Native derivations finish before their deferred callbacks, so computation blocks the caller. diff --git a/docs/content/limitations/builtin-apis/deprecated-modules.mdx b/docs/content/limitations/builtin-apis/deprecated-modules.mdx new file mode 100644 index 000000000..bb630b8c3 --- /dev/null +++ b/docs/content/limitations/builtin-apis/deprecated-modules.mdx @@ -0,0 +1 @@ +- **Deprecated `punycode` and `constants` modules:** `punycode.encode()`, `decode()`, `toASCII()`, `toUnicode()`, `ucs2.decode()`, `ucs2.encode()`, and `version` compile, with the `DEP0040` warning Node.js reports when the module loads. A `require("punycode")` made from inside `node_modules` doesn't warn; a `require()` used inside a larger expression, such as `require("punycode").toASCII(name)`, doesn't warn either. The `punycode.ucs2` object itself is unsupported as a value. The `constants` module is the frozen object Node.js builds from `os.constants` and `fs.constants`, plus the `crypto.constants` of Node.js 24.22.0. diff --git a/docs/content/limitations/builtin-apis/iterator-helpers.mdx b/docs/content/limitations/builtin-apis/iterator-helpers.mdx new file mode 100644 index 000000000..01f779cb6 --- /dev/null +++ b/docs/content/limitations/builtin-apis/iterator-helpers.mdx @@ -0,0 +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. diff --git a/docs/content/limitations/builtin-apis/map-and-set.mdx b/docs/content/limitations/builtin-apis/map-and-set.mdx index 04ebcbfca..12be44e06 100644 --- a/docs/content/limitations/builtin-apis/map-and-set.mdx +++ b/docs/content/limitations/builtin-apis/map-and-set.mdx @@ -1 +1 @@ -- **Map and Set:** Keys and elements support numbers and strings by value, and records, classes, arrays, symbols, server handles, and unions of these reference types by identity. `unknown` slots use native checked values, comparing supported primitives by value and retaining supported object references. Typed nested collections other than `Map` and `Set`, and promises requiring payload adapters, cannot pass into these slots. `Promise` retains its native representation. Stored `keys()`, `values()`, and `entries()` iterators compile, retain element identity, and observe live collection mutations until exhausted. They support `.next()`, `for...of`, array spreads, and mapper-less `Array.from`. Detached collection methods and iterator helper methods such as `.map()` and `.take()` are unsupported. The Set composition methods (`union`, `intersection`, `difference`, `symmetricDifference`, `isSubsetOf`, `isSupersetOf`, `isDisjointFrom`) accept a Set of the same element type or a Map keyed by it; other set-like arguments are unsupported. +- **Map and Set:** Keys and elements support numbers and strings by value, and records, classes, arrays, symbols, server handles, and unions of these reference types by identity. `unknown` slots use native checked values, comparing supported primitives by value and retaining supported object references. Typed nested collections other than `Map` and `Set`, and promises requiring payload adapters, cannot pass into these slots. `Promise` retains its native representation. Stored `keys()`, `values()`, and `entries()` iterators compile, retain element identity, and observe live collection mutations until exhausted. They support `.next()`, `for...of`, array spreads, and mapper-less `Array.from`. They also support the iterator helper methods, such as `.map()` and `.take()`. Detached collection methods are unsupported. The Set composition methods (`union`, `intersection`, `difference`, `symmetricDifference`, `isSubsetOf`, `isSupersetOf`, `isDisjointFrom`) accept a Set of the same element type or a Map keyed by it; other set-like arguments are unsupported. diff --git a/docs/content/limitations/builtin-apis/test-runner.mdx b/docs/content/limitations/builtin-apis/test-runner.mdx index e1baac963..6583a2873 100644 --- a/docs/content/limitations/builtin-apis/test-runner.mdx +++ b/docs/content/limitations/builtin-apis/test-runner.mdx @@ -2,3 +2,4 @@ - **Snapshots:** `t.assert.snapshot` and `t.assert.fileSnapshot` compile, with Node's default serializer, `JSON.stringify(value, null, 2)`, or an `options.serializers` array literal. A compiled test reads the snapshot file Node writes beside the entry file, `.snapshot`, at the entry file's path when the program was compiled. Running the binary with Node's flag `--test-update-snapshots` as its first argument writes the snapshot files instead; as in Node, the flag is kept out of `process.argv` and appears in `process.execArgv`. Snapshot files are read in the form Node writes them, `` exports[`key`] = `value`; `` statements. The `snapshot.setDefaultSnapshotSerializers` and `snapshot.setResolveSnapshotPath` module functions throw `SC2020`. - **TestContext values:** `t.filePath` is the entry file's path when the program was compiled. `t.attempt` is `0`, since a test runs once. `t.passed` is `false` while the body runs and the test's outcome in its `afterEach` and `t.after` hooks. `t.workerId` is `Number(process.env.NODE_TEST_WORKER_ID) || undefined`, as in Node. `t.tags` is `[]`, since the `tags` test option throws `SC2020`; the array is not frozen as Node's is. - **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. diff --git a/docs/content/limitations/language-features/class-reflection.mdx b/docs/content/limitations/language-features/class-reflection.mdx index 4e2b9f299..f2b6dc2c8 100644 --- a/docs/content/limitations/language-features/class-reflection.mdx +++ b/docs/content/limitations/language-features/class-reflection.mdx @@ -1 +1 @@ -- **Class reflection:** `JSON.stringify` and `Object.keys` require every declared field to have a representation that can pass through runtime-checked storage. They raise an error for classes with opaque fields such as native Maps. Class fields, inherited accessors, and added properties retain identity through untyped values. +- **Class reflection:** `JSON.stringify` and `Object.keys` require every declared field to have a representation that can pass through runtime-checked storage. `JSON.stringify` writes Date fields as their ISO text and Map and Set fields as `{}`, as Node.js does, and refuses classes with RegExp or Promise fields with `SC1090`. `Object.keys` raises an error for classes with opaque fields such as native Maps. Class fields, inherited accessors, and added properties retain identity through untyped values. diff --git a/docs/content/limitations/types-and-class-definitions/local-class-expressions.mdx b/docs/content/limitations/types-and-class-definitions/local-class-expressions.mdx index 1f60c6101..d18ecac60 100644 --- a/docs/content/limitations/types-and-class-definitions/local-class-expressions.mdx +++ b/docs/content/limitations/types-and-class-definitions/local-class-expressions.mdx @@ -1 +1 @@ -- **Local class expressions:** Static members, decorators, private brands, generic members, explicitly generic factories returning local constructors, and references through the class's internal name are unsupported. Supported expressions preserve fresh constructor identity and shared captures across constructors, methods, accessors, and arrow fields. Local inheritance requires a compiled base class. Constructors retain identity, `typeof`, `name`, and `length` through `unknown`; cast back to their exact type before construction. Construction and static-member access directly through `unknown` are unsupported. +- **Local class expressions:** Static members, decorators, private brands, generic members, and references through the class's internal name are unsupported. Supported expressions preserve fresh constructor identity and shared captures across constructors, methods, accessors, and arrow fields. Local inheritance requires a compiled base class. Constructors retain identity, `typeof`, `name`, and `length` through `unknown`; cast back to their exact type before construction. Construction and static-member access directly through `unknown` are unsupported. diff --git a/docs/content/limitations/webassembly-modules.mdx b/docs/content/limitations/webassembly-modules.mdx new file mode 100644 index 000000000..492b2fc1a --- /dev/null +++ b/docs/content/limitations/webassembly-modules.mdx @@ -0,0 +1,3 @@ +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 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`. diff --git a/docs/src/generated/node-v24-compatibility-meta.json b/docs/src/generated/node-v24-compatibility-meta.json index a64169933..1cf76d3e4 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": "a610bdd1ae56e1c4cd38", + "artifactVersion": "efc18bcd8a7a03fd8622", "rowCount": 3880 } diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index d4b6839e7..0e6387ea9 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -330,8 +330,8 @@ "static": { "supported": 0, "partial": 12, - "refused": 0, - "not-implemented": 80, + "refused": 1, + "not-implemented": 79, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -584,9 +584,9 @@ "apiEntries": 9, "static": { "supported": 0, - "partial": 0, + "partial": 8, "refused": 0, - "not-implemented": 9, + "not-implemented": 1, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -36590,9 +36590,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "refused", + "detail": "Explicitly rejected by a named compiler diagnostic.", + "verification": "explicit-refusal" } }, { @@ -58489,7 +58489,7 @@ "inherited": false }, "static": { - "status": "not-implemented", + "status": "partial", "detail": "Derived from the exact API rows in this chapter.", "verification": "derived" } @@ -58512,9 +58512,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" } }, { @@ -58535,9 +58535,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" } }, { @@ -58558,9 +58558,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" } }, { @@ -58581,9 +58581,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" } }, { @@ -58627,9 +58627,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" } }, { @@ -58650,9 +58650,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" } }, { @@ -58673,9 +58673,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" } }, { diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index f36c865cf..7d0fcf75c 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": 1155, - "verify-gap": 2174, - "replace-refusal": 40, + "audit-partial": 1162, + "verify-gap": 2166, + "replace-refusal": 41, "classify": 2, "implement": 85 } @@ -6736,6 +6736,8 @@ "source": "compiler-dedicated:crypto.createHash", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -6774,6 +6776,8 @@ "source": "compiler-dedicated:crypto.createHmac", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -7396,6 +7400,8 @@ "source": "compiler-dedicated:crypto.hash", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -7519,6 +7525,8 @@ "source": "compiler-dedicated:crypto.pbkdf2", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -7557,6 +7565,8 @@ "source": "compiler-dedicated:crypto.pbkdf2Sync", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -9037,6 +9047,8 @@ "source": "compiler-dedicated:Hash", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -9075,6 +9087,8 @@ "source": "compiler-dedicated:hash.copy", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -9113,6 +9127,8 @@ "source": "compiler-dedicated:hash.digest", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -9151,6 +9167,8 @@ "source": "compiler-dedicated:hash.update", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -9189,6 +9207,8 @@ "source": "compiler-dedicated:Hmac", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -9227,6 +9247,8 @@ "source": "compiler-dedicated:hmac.digest", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -9265,6 +9287,8 @@ "source": "compiler-dedicated:hmac.update", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -34178,11 +34202,11 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:globals", + "status": "refused", + "action": "replace-refusal", + "verification": "explicit-refusal", + "confidence": "high", + "source": "surface-manifest:unsupported:stdlib.webassembly", "tests": [] } } @@ -58559,12 +58583,16 @@ "priority": "low", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:punycode", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.punycode.decode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] } } }, @@ -58584,12 +58612,16 @@ "priority": "low", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:punycode", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.punycode.encode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] } } }, @@ -58609,12 +58641,16 @@ "priority": "low", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:punycode", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.punycode.toASCII", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] } } }, @@ -58634,12 +58670,16 @@ "priority": "low", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:punycode", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.punycode.toUnicode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] } } }, @@ -58684,12 +58724,16 @@ "priority": "low", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:punycode", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.punycode.ucs2.decode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] } } }, @@ -58709,12 +58753,16 @@ "priority": "low", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:punycode", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.punycode.ucs2.encode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] } } }, @@ -58734,12 +58782,16 @@ "priority": "low", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:punycode", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.punycode.version", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] } } }, diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 514282a0a..c87f98fec 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -342,8 +342,8 @@ "static": { "supported": 0, "partial": 12, - "refused": 0, - "not-implemented": 80, + "refused": 1, + "not-implemented": 79, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -611,9 +611,9 @@ "apiEntries": 9, "static": { "supported": 0, - "partial": 0, + "partial": 8, "refused": 0, - "not-implemented": 9, + "not-implemented": 1, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -19861,6 +19861,8 @@ "tests/corpus/5760-crypto-hash-stream.ts", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/node24-crypto-kdf-errors.ts", "tests/corpus/node24-crypto-kdf-options.ts", "tests/corpus/node24-crypto-kdf.ts", @@ -20402,6 +20404,8 @@ "evidence": "compiler-dedicated:crypto.createHash", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -20439,6 +20443,8 @@ "evidence": "compiler-dedicated:crypto.createHmac", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -21040,6 +21046,8 @@ "evidence": "compiler-dedicated:crypto.hash", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -21158,6 +21166,8 @@ "evidence": "compiler-dedicated:crypto.pbkdf2", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -21195,6 +21205,8 @@ "evidence": "compiler-dedicated:crypto.pbkdf2Sync", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -23095,6 +23107,8 @@ "evidence": "compiler-dedicated:Hash", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -23132,6 +23146,8 @@ "evidence": "compiler-dedicated:hash.copy", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -23169,6 +23185,8 @@ "evidence": "compiler-dedicated:hash.digest", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -23206,6 +23224,8 @@ "evidence": "compiler-dedicated:hash.update", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -23243,6 +23263,8 @@ "evidence": "compiler-dedicated:Hmac", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -23280,6 +23302,8 @@ "evidence": "compiler-dedicated:hmac.digest", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -23317,6 +23341,8 @@ "evidence": "compiler-dedicated:hmac.update", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -25031,6 +25057,8 @@ "evidence": "compiler-dedicated:crypto.pbkdf2", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -28114,6 +28142,8 @@ "evidence": "compiler-dedicated:Hash", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -28164,6 +28194,8 @@ "evidence": "compiler-dedicated:Hmac", "tests": [ "tests/corpus/5760-crypto-hash-stream.ts", + "tests/corpus/5768-crypto-hash-unknown.ts", + "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", @@ -53521,8 +53553,8 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:globals" + "status": "refused", + "evidence": "surface-manifest:unsupported:stdlib.webassembly" }, "anchorSource": "exact" }, @@ -81945,8 +81977,13 @@ "inherited": false }, "static": { - "status": "not-implemented", - "evidence": "derived:descendants" + "status": "partial", + "evidence": "derived:descendants", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "chapter" }, @@ -81968,8 +82005,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:punycode" + "status": "partial", + "evidence": "surface-manifest:node-builtin.punycode.decode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "exact" }, @@ -81991,8 +82033,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:punycode" + "status": "partial", + "evidence": "surface-manifest:node-builtin.punycode.encode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "exact" }, @@ -82014,8 +82061,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:punycode" + "status": "partial", + "evidence": "surface-manifest:node-builtin.punycode.toASCII", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "exact" }, @@ -82037,8 +82089,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:punycode" + "status": "partial", + "evidence": "surface-manifest:node-builtin.punycode.toUnicode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "exact" }, @@ -82083,8 +82140,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:punycode" + "status": "partial", + "evidence": "surface-manifest:node-builtin.punycode.ucs2.decode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "exact" }, @@ -82106,8 +82168,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:punycode" + "status": "partial", + "evidence": "surface-manifest:node-builtin.punycode.ucs2.encode", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "exact" }, @@ -82129,8 +82196,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:punycode" + "status": "partial", + "evidence": "surface-manifest:node-builtin.punycode.version", + "tests": [ + "tests/corpus/6122-punycode-conversions.ts", + "tests/corpus/6124-punycode-require-warnings.cjs", + "tests/corpus/6126-punycode-dependency/main.cjs" + ] }, "anchorSource": "chapter" }, diff --git a/internal/compatibility/src/generate.mjs b/internal/compatibility/src/generate.mjs index dfefc6e48..3433e14f9 100644 --- a/internal/compatibility/src/generate.mjs +++ b/internal/compatibility/src/generate.mjs @@ -203,6 +203,10 @@ function featureOf(row, entries, parentSignature) { function symbolCandidates(row) { const result = new Set([row.apiSymbol]); + // A global's own row (`WebAssembly`) matches the compiler's + // `globalThis.` entries. + if (row.chapter === "globals" && row.apiSymbol && !row.apiSymbol.includes(".")) + result.add(`globalThis.${row.apiSymbol}`); const aliases = [ ["fsPromises.", "fs/promises."], ["dnsPromises.", "dns/promises."], diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 45a946925..584492bf1 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -147,6 +147,7 @@ "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.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"], "node-builtin.stream": ["tests/corpus/1685-stream-readable-basics.ts", "tests/corpus/1746-stream-for-await.ts", "tests/corpus/1814-stream-pipeline.cjs", "tests/corpus/2629-stream-consumers.ts", "tests/corpus/2861-readable-iterator-close.ts"], @@ -386,7 +387,7 @@ { "symbols": ["assert.deepEqual", "assert.notDeepEqual"], "status": "partial", "evidence": ["tests/corpus/5513-assert-deep-equal-loose.js", "tests/corpus/5514-assert-deep-equal-typed.ts", "tests/corpus/5516-assert-deep-equal-edges.js"] }, { "symbols": ["assert", "assert.ok", "assert.equal", "assert.notEqual", "assert.strictEqual", "assert.notStrictEqual", "assert.deepStrictEqual", "assert.notDeepStrictEqual", "assert.fail", "assert.match", "assert.doesNotMatch", "assert.throws", "assert.rejects", "assert.ifError"], "status": "partial", "evidence": ["tests/corpus/1600-assert-passing.ts", "tests/corpus/1604-assert-deep-structures.ts", "tests/corpus/1609-assert-async.ts", "tests/corpus/1721-assert-throws-regex-class.ts", "tests/corpus/2936-static-loose-equality.ts", "tests/corpus/5515-assert-deep-strict-typed-messages.ts"] }, { "chapter": "crypto", "symbols": ["Cipheriv", "cipher.update", "cipher.final", "cipher.setAAD", "cipher.getAuthTag", "cipher.setAutoPadding", "Decipheriv", "decipher.update", "decipher.final", "decipher.setAAD", "decipher.setAuthTag", "decipher.setAutoPadding"], "status": "partial", "evidence": ["tests/corpus/5250-cipher-aes-gcm.ts", "tests/corpus/5251-cipher-aes-cbc.ts", "tests/corpus/5252-cipher-aes-ctr.ts", "tests/corpus/5253-cipher-chacha20-poly1305.ts", "tests/corpus/5254-cipher-errors.js", "tests/corpus/5255-cipher-encodings.cjs", "tests/corpus/5256-cipher-deprecation.cjs", "tests/corpus/5257-cipher-vectors.ts", "tests/corpus/5258-cipher-handles.js", "tests/corpus/5259-cipher-chunked.ts", "tests/fixtures/node-types/cipher.ts"] }, - { "symbols": ["crypto.createHash", "Hash", "hash.update", "hash.digest", "hash.copy", "crypto.createHmac", "Hmac", "hmac.update", "hmac.digest", "crypto.hash", "crypto.pbkdf2", "crypto.pbkdf2Sync"], "status": "partial", "evidence": ["tests/corpus/5760-crypto-hash-stream.ts", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", "tests/corpus/node24-crypto-sha2.ts", "tests/corpus/5435-crypto-hash-algorithms.ts", "tests/corpus/5436-crypto-hash-names.js", "tests/corpus/5437-crypto-hmac-algorithms.ts", "tests/corpus/5438-crypto-kdf-digests.ts", "tests/corpus/5440-crypto-hash-encodings.js", "tests/corpus/5441-crypto-hash-output-length.ts", "tests/harness/npm-static.test.ts"] }, + { "symbols": ["crypto.createHash", "Hash", "hash.update", "hash.digest", "hash.copy", "crypto.createHmac", "Hmac", "hmac.update", "hmac.digest", "crypto.hash", "crypto.pbkdf2", "crypto.pbkdf2Sync"], "status": "partial", "evidence": ["tests/corpus/5760-crypto-hash-stream.ts", "tests/corpus/5768-crypto-hash-unknown.ts", "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", "tests/corpus/node24-crypto-sha2.ts", "tests/corpus/5435-crypto-hash-algorithms.ts", "tests/corpus/5436-crypto-hash-names.js", "tests/corpus/5437-crypto-hmac-algorithms.ts", "tests/corpus/5438-crypto-kdf-digests.ts", "tests/corpus/5440-crypto-hash-encodings.js", "tests/corpus/5441-crypto-hash-output-length.ts", "tests/harness/npm-static.test.ts"] }, { "symbols": ["crypto.randomFill", "crypto.randomFillSync", "crypto.randomUUID", "crypto.randomUUIDv7"], "status": "supported", "evidence": ["tests/corpus/5442-crypto-random-values.ts", "tests/corpus/5443-crypto-random-arguments.js", "tests/corpus/1358-crypto-random.ts"] }, { "symbols": ["crypto.createSecretKey", "crypto.generateKey", "crypto.generateKeySync", "KeyObject", "keyObject.export", "keyObject.equals", "keyObject.symmetricKeySize", "keyObject.type"], "status": "partial", "evidence": ["tests/corpus/5445-crypto-secret-key-objects.ts", "tests/corpus/5446-crypto-secret-key-errors.js"] }, { "symbols": ["crypto.timingSafeEqual"], "status": "supported", "evidence": ["tests/corpus/5444-crypto-timing-safe-equal.js", "tests/corpus/2890-crypto-static-utility.ts"] }, diff --git a/native/llvm-codegen/src/partition.cpp b/native/llvm-codegen/src/partition.cpp index 066ba614c..8ad696d33 100644 --- a/native/llvm-codegen/src/partition.cpp +++ b/native/llvm-codegen/src/partition.cpp @@ -418,7 +418,12 @@ std::optional partitionProgramText(StringRef Text, // An internal function referenced by exactly one other function, and by // no global, is a whole-program inlining candidate; keep it with that - // function so its partition can inline it without importing. + // function so its partition can inline it without importing. A function + // body larger than this is past LLVM's inline cost limit even for the only + // call to a local function, so it is placed by its own name. Otherwise a + // program's module initializers, which only the entry point calls, would + // all share the entry point's partition. + constexpr size_t MaxInlinedBodyBytes = 256 * 1024; constexpr unsigned Shared = ~1u; std::vector Referrer(Symbols.size(), NoPartition); for (unsigned I = 0; I < Symbols.size(); ++I) { @@ -441,7 +446,8 @@ std::optional partitionProgramText(StringRef Text, for (unsigned Steps = 0; Steps < 1024; ++Steps) { const Symbol &S = Symbols[Root]; unsigned Parent = Referrer[Root]; - if (!S.Local || Parent == NoPartition || Parent == Shared || + if (!S.Local || S.Body.size() > MaxInlinedBodyBytes || + Parent == NoPartition || Parent == Shared || Symbols[Parent].Kind != SymbolKind::Function || Parent == I) break; Root = Parent; diff --git a/native/llvm-codegen/src/partition.h b/native/llvm-codegen/src/partition.h index 9d48548fb..838919443 100644 --- a/native/llvm-codegen/src/partition.h +++ b/native/llvm-codegen/src/partition.h @@ -22,8 +22,8 @@ struct ProgramPartitions { // Divides the LLVM assembly scriptc emits into Count modules without parsing // it, so the partitions can be parsed concurrently. Functions are placed by a // stable hash of their name; an internal function referenced by exactly one -// other function stays with that function, and a global stays with the first -// function that references it. Returns nullopt for any construct outside the +// other function stays with that function unless its body is too large to +// inline, and a global stays with the first function that references it. Returns nullopt for any construct outside the // emitter's narrow top-level grammar; callers then split a parsed module. std::optional partitionProgramText(llvm::StringRef Text, unsigned Count); diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index d72ca2dcd..cc5de935c 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -3762,6 +3762,39 @@ declare module "node:querystring" { export * from "querystring"; } +/* node:punycode (deprecated, DEP0040) — punycode.js 2.1.0: decode and + * toUnicode throw its RangeErrors on malformed input; ucs2 converts + * between strings and UTF-16 code point arrays. */ +declare module "punycode" { + export function decode(string: string): string; + export function encode(string: string): string; + export function toUnicode(domain: string): string; + export function toASCII(domain: string): string; + export namespace ucs2 { + function decode(string: string): number[]; + function encode(codePoints: readonly number[]): string; + } + export const version: string; +} +declare module "node:punycode" { + export * from "punycode"; +} + +/* node:constants (deprecated, DEP0008) — one frozen object: os.constants' + * dlopen, errno, priority and signals groups, fs.constants, and + * crypto.constants. A platform's missing constant reads as undefined. */ +declare module "constants" { + const constants: { readonly [name: string]: number } & { + readonly defaultCoreCipherList: string; + readonly defaultCipherList: string; + }; + export = constants; +} +declare module "node:constants" { + import constants = require("constants"); + export = constants; +} + /* node:readline — the question/close slice: createInterface over exactly * { input: process.stdin, output: process.stdout }, question(query, cb) * (the query writes to stdout, the callback gets the next line's text), diff --git a/packages/compiler/ambient/scriptc.d.ts b/packages/compiler/ambient/scriptc.d.ts index 424e49065..cec26f139 100644 --- a/packages/compiler/ambient/scriptc.d.ts +++ b/packages/compiler/ambient/scriptc.d.ts @@ -13,6 +13,9 @@ * they MERGE as overloads instead of colliding — the Timeout handle maps * to the numeric timer id either way, and .unref()/.ref()/.hasRef() are * loop-liveness bookkeeping). + * 4. The WebAssembly namespace and BufferSource, which the DOM library + * supplies in ordinary TypeScript projects (types only; every reached + * use of the API is refused at its site). * * This file ships to EVERY program scriptc builds — the lowering program * and preflight's project-world second-chance program alike (a project @@ -108,3 +111,134 @@ declare function clearTimeout(handle?: Timeout | number | null | undefined): voi * baked result is exactly what Node would compute at runtime (the * differential harness verifies this with a `comptime = (f) => f()` shim). */ declare function comptime(compute: () => T): T; + +/* The JavaScript WebAssembly API, as the DOM library declares it (scriptc's + * ES-only type world never loads lib.dom, and @types/node leaves it to + * lib.dom). Packages whose declarations name these types (quickjs-wasi, + * photon-node, ...) typecheck against them. These are types only: the + * type mapper gives every type declared here the checked-dynamic + * representation, and the lowerer refuses each reached use of the API with + * a located diagnostic (frontend/lowering/builtins/webassembly.ts). + * BufferSource admits every ArrayBufferView, as lib.dom did before + * TypeScript 5.7: Node's Buffer types are views over ArrayBufferLike. */ +type BufferSource = ArrayBufferView | ArrayBuffer; +declare namespace WebAssembly { + interface CompileError extends Error {} + var CompileError: { + prototype: CompileError; + new (message?: string): CompileError; + (message?: string): CompileError; + }; + interface Global { + value: ValueTypeMap[T]; + valueOf(): ValueTypeMap[T]; + } + var Global: { + prototype: Global; + new ( + descriptor: GlobalDescriptor, + v?: ValueTypeMap[T], + ): Global; + }; + interface Instance { + readonly exports: Exports; + } + var Instance: { + prototype: Instance; + new (module: Module, importObject?: Imports): Instance; + }; + interface LinkError extends Error {} + var LinkError: { + prototype: LinkError; + new (message?: string): LinkError; + (message?: string): LinkError; + }; + interface Memory { + readonly buffer: ArrayBuffer; + grow(delta: number): number; + } + var Memory: { + prototype: Memory; + new (descriptor: MemoryDescriptor): Memory; + }; + interface Module {} + var Module: { + prototype: Module; + new (bytes: BufferSource): Module; + customSections(moduleObject: Module, sectionName: string): ArrayBuffer[]; + exports(moduleObject: Module): ModuleExportDescriptor[]; + imports(moduleObject: Module): ModuleImportDescriptor[]; + }; + interface RuntimeError extends Error {} + var RuntimeError: { + prototype: RuntimeError; + new (message?: string): RuntimeError; + (message?: string): RuntimeError; + }; + interface Table { + readonly length: number; + get(index: number): any; + grow(delta: number, value?: any): number; + set(index: number, value?: any): void; + } + var Table: { + prototype: Table; + new (descriptor: TableDescriptor, value?: any): Table; + }; + interface GlobalDescriptor { + mutable?: boolean; + value: T; + } + interface MemoryDescriptor { + initial: number; + maximum?: number; + shared?: boolean; + } + interface ModuleExportDescriptor { + kind: ImportExportKind; + name: string; + } + interface ModuleImportDescriptor { + kind: ImportExportKind; + module: string; + name: string; + } + interface TableDescriptor { + element: TableKind; + initial: number; + maximum?: number; + } + interface ValueTypeMap { + anyfunc: Function; + externref: any; + f32: number; + f64: number; + i32: number; + i64: bigint; + v128: never; + } + interface WebAssemblyInstantiatedSource { + instance: Instance; + module: Module; + } + type ImportExportKind = "function" | "global" | "memory" | "table"; + type TableKind = "anyfunc" | "externref"; + type ExportValue = Function | Global | Memory | Table; + type Exports = Record; + type ImportValue = ExportValue | number; + type Imports = Record; + type ModuleImports = Record; + type ValueType = keyof ValueTypeMap; + function compile(bytes: BufferSource): Promise; + function compileStreaming(source: Response | PromiseLike): Promise; + function instantiate( + bytes: BufferSource, + importObject?: Imports, + ): Promise; + function instantiate(moduleObject: Module, importObject?: Imports): Promise; + function instantiateStreaming( + source: Response | PromiseLike, + importObject?: Imports, + ): Promise; + function validate(bytes: BufferSource): boolean; +} diff --git a/packages/compiler/src/backend/llvm/alloc.test.ts b/packages/compiler/src/backend/llvm/alloc.test.ts index 5ba7ff3d1..49971c3ce 100644 --- a/packages/compiler/src/backend/llvm/alloc.test.ts +++ b/packages/compiler/src/backend/llvm/alloc.test.ts @@ -1,5 +1,11 @@ import { describe, expect, test } from "vitest"; -import { type AllocHost, emitObjectAlloc, emitObjectFree, inlineAllocSupported } from "./alloc.js"; +import { + type AllocHost, + disposeHookLoadLines, + emitObjectAlloc, + emitObjectFree, + inlineAllocSupported, +} from "./alloc.js"; function host(inlineAlloc: boolean): AllocHost & { decls: Set; oom: boolean } { const h = { @@ -63,7 +69,12 @@ describe("emitted object allocation", () => { ]); expect(emitObjectFree(h, false)).toEqual([ " call void @scr_obj_free_note()", + " %sf.obs = load i1, ptr @sc_weak_keys", + " br i1 %sf.obs, label %sf.observe, label %sf.block", + "sf.observe:", " call void @scr_weak_dispose(ptr %o)", + " br label %sf.block", + "sf.block:", " call void @scr_rt_free(ptr %o)", ]); expect([...h.decls].some((d) => d.includes("@scr_sa"))).toBe(false); @@ -167,4 +178,30 @@ describe("emitted object allocation", () => { expect(lines.at(-1)).toBe("sf.done:"); } }); + + test("frees test the dispose hook only where the program can observe them", () => { + const h = host(true); + const weak = emitObjectFree(h, false); + expectSingleDefinitions(weak); + expect(weak.slice(0, 2)).toEqual([ + " %sf.obs = load i1, ptr @sc_weak_keys", + " br i1 %sf.obs, label %sf.hooked, label %sf.block", + ]); + const records = emitObjectFree(h, true, "weak-or-source"); + expectSingleDefinitions(records); + expect(records.slice(0, 4)).toEqual([ + " %sf.obsw = load i1, ptr @sc_weak_keys", + " %sf.obss = load i1, ptr @sc_record_sources", + " %sf.obs = or i1 %sf.obsw, %sf.obss", + " br i1 %sf.obs, label %sf.hooked, label %sf.block", + ]); + expect(records).toContain(" call void %sf.hook(ptr %o)"); + }); + + test("loads the dispose hook from emitted code only with the inline allocator", () => { + expect(disposeHookLoadLines(host(false), "%t1")).toBeNull(); + expect(disposeHookLoadLines(host(true), "%t1")).toEqual([ + " %t1 = load ptr, ptr @scr_weak_dispose_hook", + ]); + }); }); diff --git a/packages/compiler/src/backend/llvm/alloc.ts b/packages/compiler/src/backend/llvm/alloc.ts index 7dfc385aa..7fdc4b683 100644 --- a/packages/compiler/src/backend/llvm/alloc.ts +++ b/packages/compiler/src/backend/llvm/alloc.ts @@ -79,6 +79,17 @@ function threadLocalState(host: AllocHost, prefix: string, lines: string[]): str return [...preamble, ...text.split("\n")]; } +/** Lines that load the runtime's dispose hook into `dest`, or null on + * targets without the inline allocator, where the hook's storage is not + * addressed from emitted code. The hook is NULL until a weak collection + * or a kept record source installs it, and then never NULL again. */ +export function disposeHookLoadLines(host: AllocHost, dest: string): string[] | null { + if (host.inlineAlloc !== true) return null; + declareGlobal(host, `@scr_weak_dispose_hook = external global ptr`); + const prefix = dest.replace(/^%/, "").replace(/[^-$._A-Za-z0-9]/g, "_"); + return threadLocalState(host, prefix, [` ${dest} = load ptr, ptr @scr_weak_dispose_hook`]); +} + /** Worker executables: the calling script thread's ScrThreadHot block into * `dest`. Its address also identifies the thread (the owner word of a * lifetime-region chunk; llvm/regions.ts). */ @@ -325,20 +336,56 @@ function allocLines( return lines; } +/* ── dispose observers ──────────────────────────────────────────────── + * The runtime's dispose hook (scr_weak_dispose_hook) has two clients: weak + * collections, whose keys can be any object, and the kept sources of + * checked records (scr_record_source_note and the JSON schema parser), + * keyed by closed records. A program without either can never install the + * hook, so its frees need not test it. Whether the program has them is + * known only once every helper is emitted (dynCheck builders and JSON + * schemas are requested lazily), so the emitted frees and record writers + * test these two module constants, which the emitter defines last + * (LlvmEmitter.emitParts) and LLVM folds away. */ + +/** True when the program constructs a WeakMap or WeakSet. */ +export const WEAK_KEYS_GATE = "@sc_weak_keys"; +/** True when a checked conversion can keep a record's undeclared source. */ +export const RECORD_SOURCES_GATE = "@sc_record_sources"; + +/** Which dispose observers an object type can have: weak-collection keys + * only, or (closed record layouts) kept record sources too. */ +export type FreeObservers = "weak" | "weak-or-source"; + /** * Lines that free the object `%o` after its members were released: the - * RC-audit free note, the weak-collection dispose hook, and the block — - * `scr_cyc_free` for collector-headered objects, `scr_rt_free` otherwise. - * Labels use the `sf.` prefix; the lines end in an open block. + * RC-audit free note, the dispose hook (when the program can observe this + * type's disposals), and the block — `scr_cyc_free` for collector-headered + * objects, `scr_rt_free` otherwise. Labels use the `sf.` prefix; the lines + * end in an open block. */ -export function emitObjectFree(host: AllocHost, cyc: boolean): string[] { - const lines = threadLocalState(host, "sf", freeLines(host, cyc)); +export function emitObjectFree( + host: AllocHost, + cyc: boolean, + observers: FreeObservers = "weak", +): string[] { + const lines = threadLocalState(host, "sf", freeLines(host, cyc, observers)); if (host.objectAudit === false) return lines.filter((line) => line !== FREE_NOTE); host.declare(`declare void @scr_obj_free_note()`); return lines; } -function freeLines(host: AllocHost, cyc: boolean): string[] { +/** Lines that leave in `%.obs` whether the program can observe a + * disposal of this type (folds to a constant). */ +function observedLines(prefix: string, observers: FreeObservers): string[] { + if (observers === "weak") return [` %${prefix}.obs = load i1, ptr ${WEAK_KEYS_GATE}`]; + return [ + ` %${prefix}.obsw = load i1, ptr ${WEAK_KEYS_GATE}`, + ` %${prefix}.obss = load i1, ptr ${RECORD_SOURCES_GATE}`, + ` %${prefix}.obs = or i1 %${prefix}.obsw, %${prefix}.obss`, + ]; +} + +function freeLines(host: AllocHost, cyc: boolean, observers: FreeObservers): string[] { if (host.inlineAlloc !== true) { if (cyc) { host.declare(`declare void @scr_cyc_free(ptr)`); @@ -348,7 +395,12 @@ function freeLines(host: AllocHost, cyc: boolean): string[] { host.declare(`declare void @scr_weak_dispose(ptr)`); return [ ` call void @scr_obj_free_note()`, + ...observedLines("sf", observers), + ` br i1 %sf.obs, label %sf.observe, label %sf.block`, + `sf.observe:`, ` call void @scr_weak_dispose(ptr %o)`, + ` br label %sf.block`, + `sf.block:`, ` call void @scr_rt_free(ptr %o)`, ]; } @@ -356,11 +408,14 @@ function freeLines(host: AllocHost, cyc: boolean): string[] { host.declare(`declare void @scr_rt_free(ptr)`); declareGlobal(host, `@scr_weak_dispose_hook = external global ptr`); const lines = [ + ...observedLines("sf", observers), + ` br i1 %sf.obs, label %sf.hooked, label %sf.block`, + `sf.hooked:`, ` %sf.hook = load ptr, ptr @scr_weak_dispose_hook`, ` %sf.has = icmp ne ptr %sf.hook, null`, ` %sf.hasx = call i1 @llvm.expect.i1(i1 %sf.has, i1 false)`, ` br i1 %sf.hasx, label %sf.observe, label %sf.block`, - `sf.observe: ; weak collections observe the disposal`, + `sf.observe: ; a weak collection or a kept record source observes the disposal`, ` call void %sf.hook(ptr %o)`, ` br label %sf.block`, `sf.block:`, diff --git a/packages/compiler/src/backend/llvm/dense-array-access.ts b/packages/compiler/src/backend/llvm/dense-array-access.ts index 183bb8239..f19073b75 100644 --- a/packages/compiler/src/backend/llvm/dense-array-access.ts +++ b/packages/compiler/src/backend/llvm/dense-array-access.ts @@ -47,7 +47,8 @@ interface DenseGuard { * then extends the length over hole states exactly the same way). An index * below the hole-free prefix bound branches to `filledLabel` first; both * that block and the returned (state-checked) dense block may use the - * guard's values. */ + * guard's values. A `proven` index is known to lie below the bound (a dense + * array loop's fast copy, dense-array-loops.ts) and always takes it. */ function emitDenseGuard( host: LlvmEmitterContext, array: string, @@ -56,6 +57,7 @@ function emitDenseGuard( slow: string, requireBelowLen: boolean, filledLabel: string, + proven = false, ): DenseGuard { const B = host.B; const size = host.sizeType; @@ -137,8 +139,10 @@ function emitDenseGuard( B.line(`${offset} = trunc i64 ${wide} to i32`); } // The bound never exceeds the length or the dense capacity. - let inFilled = B.tmp(); - B.line(`${inFilled} = icmp ult i64 ${wide}, ${filledWide}`); + // The proof covers the counter's integer value only. + const below = proven && integral === null; + let inFilled = below ? "true" : B.tmp(); + if (!below) B.line(`${inFilled} = icmp ult i64 ${wide}, ${filledWide}`); if (integral) { const t = B.tmp(); B.line(`${t} = and i1 ${integral}, ${inFilled}`); @@ -272,6 +276,7 @@ export function emitDenseArrayGet( element: IrType, getter: string, retain: boolean, + proven = false, ): string { const B = host.B; const ty = acc === "f64" ? "double" : acc === "bool" ? "i1" : "ptr"; @@ -285,7 +290,7 @@ export function emitDenseArrayGet( const slow = B.newLabel("arr.get.slow"), join = B.newLabel("arr.get.join"), valueLabel = B.newLabel("arr.get.value"); - const guard = emitDenseGuard(host, array, index, indexExpr, slow, true, valueLabel); + const guard = emitDenseGuard(host, array, index, indexExpr, slow, true, valueLabel, proven); const state = loadState(host, guard); const present = B.tmp(); B.line(`${present} = icmp eq i8 ${state}, ${SCR_ARR_VALUE}`); @@ -331,6 +336,7 @@ export function emitDenseArrayNumber( array: string, index: LlValue, indexExpr: IrExpr | undefined, + proven = false, ): string { return emitDenseArrayGet( host, @@ -341,6 +347,7 @@ export function emitDenseArrayNumber( { kind: "f64" }, "scr_arr_get_number", false, + proven, ); } @@ -399,6 +406,7 @@ export function emitDenseArraySet( acc: ArrayAccess, element: IrType, value: string, + proven = false, ): void { const B = host.B; const ty = acc === "f64" ? "double" : acc === "bool" ? "i1" : "ptr"; @@ -413,7 +421,7 @@ export function emitDenseArraySet( const slow = B.newLabel("arr.set.slow"), join = B.newLabel("arr.set.join"), filledLabel = B.newLabel("arr.set.filled"); - const guard = emitDenseGuard(host, array, index, indexExpr, slow, false, filledLabel); + const guard = emitDenseGuard(host, array, index, indexExpr, slow, false, filledLabel, proven); const { offset, len } = guard; const storeValue = (valuePtr: string): void => { if (acc === "f64") { diff --git a/packages/compiler/src/backend/llvm/dense-array-loops.test.ts b/packages/compiler/src/backend/llvm/dense-array-loops.test.ts new file mode 100644 index 000000000..405ad67e7 --- /dev/null +++ b/packages/compiler/src/backend/llvm/dense-array-loops.test.ts @@ -0,0 +1,147 @@ +import { expect, test } from "vitest"; +import { BOOL, F64, arrayOf, type IrExpr, type IrLocal, type IrStmt } from "../../ir/ir.js"; +import { + countedDenseShape, + matchDenseArrayLoop, + type DenseLoopShape, +} from "./dense-array-loops.js"; + +const loc = { file: "dense-array-loops.ts", start: 0, end: 0 }; +const ARR = arrayOf(F64); +const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); +const ref = (localId: string): IrExpr => ({ kind: "varRef", localId, type: F64, loc }); +const arr = (localId: string): IrExpr & { kind: "varRef" } => ({ + kind: "varRef", + localId, + type: ARR, + loc, +}); +const method = ( + receiver: string, + name: Extract["method"], + args: IrExpr[] = [], +): IrExpr => ({ + kind: "arrIntrinsic", + method: name, + receiver: arr(receiver), + args, + type: F64, + loc, +}); +const read = (receiver: string, index = ref("i")): IrExpr => method(receiver, "getNumber", [index]); +const write = (receiver: string, value: IrExpr, index = ref("i")): IrStmt => ({ + kind: "arraySet", + arr: arr(receiver), + index, + value, + loc, +}); +const locals = new Map([ + ["i", { id: "i", name: "i", type: F64, mutable: true }], + ["a", { id: "a", name: "a", type: ARR, mutable: false }], + ["b", { id: "b", name: "b", type: ARR, mutable: true }], + ["c", { id: "c", name: "c", type: ARR, mutable: false }], +]); + +/** for (let i = 0; i < a.length; i++) body */ +function loop(body: IrStmt[]): IrStmt & { kind: "for" } { + return { + kind: "for", + loc, + init: { kind: "varDecl", localId: "i", init: num(0), loc }, + cond: { kind: "bin", op: "<", left: ref("i"), right: method("a", "length"), type: BOOL, loc }, + update: { + kind: "assign", + localId: "i", + value: { kind: "bin", op: "+", left: ref("i"), right: num(1), type: F64, loc }, + loc, + }, + body, + }; +} +const shape: DenseLoopShape = { + counter: "i", + length: arr("a"), + range: { min: 0, max: 4294967294 }, +}; +const match = (body: IrStmt[], initialized = new Set(["a", "b"])) => + matchDenseArrayLoop(loop(body), shape, locals, new Set(), initialized); + +test("arrays indexed at the counter of a length-preserving loop get an int32 fast copy", () => { + const scale = write("b", { + kind: "bin", + op: "*", + left: read("a"), + right: num(2), + type: F64, + loc, + }); + const dense = match([scale]); + expect([...dense!.arrays.keys()].sort()).toEqual(["a", "b"]); + expect(dense!.range).toEqual({ min: 0, max: 2147483646 }); + // A binding not definitely assigned before the loop is not read by the guard. + expect([...match([scale], new Set())!.arrays.keys()]).toEqual(["a"]); +}); + +test("loops that may resize, rebind or move storage keep the ordinary copy only", () => { + expect(match([{ kind: "exprStmt", expr: method("a", "push", [num(1)]), loc }])).toBeNull(); + expect(match([write("a", num(0), ref("unknown"))])).toBeNull(); + expect( + match([ + { kind: "exprStmt", expr: { kind: "call", callee: "f", args: [], type: F64, loc }, loc }, + ]), + ).toBeNull(); + // A nested loop would be duplicated too. + expect(match([{ kind: "exprStmt", expr: read("a"), loc }, loop([])])).toBeNull(); + // No access at the counter: nothing to prove. + expect(match([{ kind: "exprStmt", expr: read("a", num(0)), loc }])).toBeNull(); + // Copying the bounding array into another binding lets it escape. + expect( + match([ + { kind: "exprStmt", expr: read("a"), loc }, + { kind: "assign", localId: "b", value: arr("a"), loc }, + ]), + ).toBeNull(); + // A rebound array is not covered, but the bounding array still is. + const rebound = match([ + { kind: "exprStmt", expr: read("b"), loc }, + { kind: "exprStmt", expr: read("a"), loc }, + { kind: "assign", localId: "b", value: arr("c"), loc }, + ]); + expect([...rebound!.arrays.keys()]).toEqual(["a"]); +}); + +test("counted shapes need a non-negative counter strictly below the length", () => { + const ascending = (limit: IrExpr, inclusive: boolean, min = 0) => + countedDenseShape(loop([]), { + localId: "i", + start: num(min), + limit, + step: 1, + inclusive, + range: { min, max: 100 }, + }); + const minus = (offset: number): IrExpr => ({ + kind: "bin", + op: "-", + left: method("a", "length"), + right: num(offset), + type: F64, + loc, + }); + expect(ascending(method("a", "length"), false)?.length.localId).toBe("a"); + expect(ascending(minus(1), true)?.length.localId).toBe("a"); + expect(ascending(method("a", "length"), true)).toBeNull(); + expect(ascending(minus(1), false, -1)).toBeNull(); + const descending = (offset: number) => + countedDenseShape(loop([]), { + localId: "i", + start: minus(offset), + limit: num(0), + step: -1, + inclusive: true, + range: { min: 0, max: 100 }, + }); + expect(descending(1)?.length.localId).toBe("a"); + expect(descending(0)).toBeNull(); +}); diff --git a/packages/compiler/src/backend/llvm/dense-array-loops.ts b/packages/compiler/src/backend/llvm/dense-array-loops.ts new file mode 100644 index 000000000..271a5ddf8 --- /dev/null +++ b/packages/compiler/src/backend/llvm/dense-array-loops.ts @@ -0,0 +1,215 @@ +/* Dense array loops: a versioned copy of an index loop over an array whose + * length the loop never changes. + * + * `for (let i = 0; i < a.length; i++) b[i] = a[i] * 2` reads its bound from + * `a`, and every element access at the counter normally tests the index + * against the array's hole-free prefix bound (`filled`, see + * dense-array-access.ts) before it touches the slot. When the loop provably + * never changes `a.length` (ir/array-length-stability.ts) and never moves or + * cuts the storage of the arrays it indexes, one test before the loop covers + * every pass: if `a.length` is at most each indexed array's `filled` bound, + * every counter value the loop can reach (at least 0, below `a.length`) + * names a present value in dense storage. The fast copy then reads and + * replaces those slots directly, which leaves LLVM a loop without + * per-element branches (vectorizable where the body allows it). The same + * test bounds `a.length` by 2^31 - 1, so the fast copy's counter is an int32. + * Arrays longer than that are sparse in practice (a dense one would need + * 16 GB of slots) and take the ordinary copy, as does every array with a + * hole, present undefined or sparse storage below `a.length`. */ + +import type { IrExpr, IrLocal, IrStmt } from "../../ir/ir.js"; +import type { IntegerRange } from "../../ir/integer-ranges.js"; +import { preservesArrayLength } from "../../ir/array-length-stability.js"; +import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; +import type { LlvmEmitterContext } from "./expr-context.js"; +import { SCR_ARR_FIELD } from "./common.js"; + +export const INT32_MAX = 2147483647; + +export interface DenseArrayLoop { + /** The loop's counter binding. */ + counter: string; + /** The array whose length bounds the counter. */ + length: IrExpr & { kind: "varRef" }; + /** Arrays whose accesses at the counter are proven to be inside the + * hole-free prefix in the fast copy, with a read of each binding. */ + arrays: ReadonlyMap; + /** The counter's range on every pass of the fast copy. */ + range: IntegerRange; +} + +/** The fast copy duplicates the loop; keep the duplicated code small. */ +const MAX_LOOP_NODES = 120; + +/** How a matched integer loop bounds its counter below `length.length`. */ +export interface DenseLoopShape { + counter: string; + length: IrExpr & { kind: "varRef" }; + /** The counter's proven range on every pass of the ordinary loop; the + * fast copy keeps its minimum, which must be at least 0. */ + range: IntegerRange; +} + +/** `length.length - c` (c an integer literal, 0 when absent). */ +function lengthOffset(e: IrExpr): { receiver: IrExpr & { kind: "varRef" }; offset: number } | null { + let offset = 0; + if ( + e.kind === "bin" && + e.op === "-" && + e.right.kind === "numLit" && + Number.isSafeInteger(e.right.value) + ) { + offset = e.right.value; + e = e.left; + } + return e.kind === "arrIntrinsic" && + e.method === "length" && + e.args.length === 0 && + e.receiver.kind === "varRef" && + e.receiver.type.kind === "array" + ? { receiver: e.receiver, offset } + : null; +} + +/** The array bounding a counted loop's counter below its length: an + * ascending loop compared below `a.length - c`, or a descending one starting + * at `a.length - c` (c >= 1). */ +export function countedDenseShape( + loop: IrStmt & { kind: "for" }, + counted: { + localId: string; + start: IrExpr; + limit: IrExpr; + step: number; + inclusive: boolean; + range: IntegerRange; + }, +): DenseLoopShape | null { + if (counted.range.min < 0) return null; + if (counted.step > 0) { + const bound = lengthOffset(counted.limit); + if (!bound || bound.offset < (counted.inclusive ? 1 : 0)) return null; + return { counter: counted.localId, length: bound.receiver, range: counted.range }; + } + // The start is evaluated once, before the loop; the counter only falls. + const start = lengthOffset(counted.start); + if (!start || start.offset < 1 || loop.init?.kind !== "varDecl") return null; + return { counter: counted.localId, length: start.receiver, range: counted.range }; +} + +/** Match the fast copy of a loop of the given shape. `initialized` holds the + * bindings definitely assigned when the loop starts; `unaliased` the local + * arrays no other binding holds. */ +export function matchDenseArrayLoop( + loop: IrStmt & { kind: "for" }, + shape: DenseLoopShape, + locals: ReadonlyMap, + unaliased: ReadonlySet, + initialized: ReadonlySet, +): DenseArrayLoop | null { + const { counter, length } = shape; + if (shape.range.min < 0 || !loop.cond) return null; + let nodes = 0, + nested = false; + const candidates = new Map(); + // Arrays written, resized, or indexed other than at the counter by an + // element write. + const disturbed = new Set(); + const access = (arr: IrExpr, index: IrExpr): void => { + if (arr.kind === "varRef" && index.kind === "varRef" && index.localId === counter) + candidates.set(arr.localId, arr); + }; + const expr = (e: IrExpr): boolean => { + if (++nodes > MAX_LOOP_NODES) return false; + if (e.kind === "assignExpr" || e.kind === "incDec") disturbed.add(e.localId); + if (e.kind === "arrayGet") access(e.arr, e.index); + if (e.kind === "arrIntrinsic") { + if (e.method === "getNumber" && e.args[0]) access(e.receiver, e.args[0]); + else if (e.method !== "length" && e.receiver.kind === "varRef") + disturbed.add(e.receiver.localId); + } + return everyExprChild(e, expr, stmt); + }; + const stmt = (s: IrStmt): boolean => { + if (++nodes > MAX_LOOP_NODES) return false; + if (s.kind === "for" || s.kind === "while" || s.kind === "doWhile" || s.kind === "forOf") + nested = true; + if (s.kind === "assign" || s.kind === "varDecl") disturbed.add(s.localId); + if (s.kind === "arraySet") { + access(s.arr, s.index); + if (s.arr.kind === "varRef" && (s.index.kind !== "varRef" || s.index.localId !== counter)) + disturbed.add(s.arr.localId); + } + return everyStmtChild(s, expr, stmt); + }; + if (!loop.body.every(stmt) || !expr(loop.cond) || nested) return null; + if ( + !preservesArrayLength( + [loop.cond, ...loop.body], + { receiver: length.localId, inRange: counter }, + locals, + unaliased, + ) + ) + return null; + const arrays = new Map(); + for (const [id, ref] of candidates) { + const local = locals.get(id); + if ( + !local || + local.type.kind !== "array" || + local.boxed || + local.tdz || + disturbed.has(id) || + (id !== length.localId && !initialized.has(id)) + ) + continue; + arrays.set(id, ref); + } + if (arrays.size === 0) return null; + return { + counter, + length, + arrays, + range: { min: shape.range.min, max: Math.min(shape.range.max, INT32_MAX - 1) }, + }; +} + +/** The fast copy's entry test: the bounding length is at most 2^31 - 1 and + * at most every indexed array's hole-free prefix bound. */ +export function emitDenseLoopGuard(host: LlvmEmitterContext, loop: DenseArrayLoop): string { + const B = host.B; + const size = host.sizeType; + const header = (array: string, field: number): string => { + const ptr = B.tmp(); + B.line(`${ptr} = getelementptr inbounds %ScrArr, ptr ${array}, i32 0, i32 ${field}`); + host.markMemoryPointer(ptr, "array:header"); + return ptr; + }; + const receiver = host.emitStableReceiver(loop.length, []); + const lenPtr = header(receiver.name, 1), + len = B.tmp(), + small = B.tmp(); + B.line( + `${len} = load ${size}, ptr ${lenPtr}${host.fieldAliasAttachment(lenPtr)}${host.arrayLengthRange}`, + ); + B.line(`${small} = icmp ule ${size} ${len}, ${INT32_MAX}`); + let valid = small; + for (const [id, ref] of loop.arrays) { + const array = id === loop.length.localId ? receiver : host.emitStableReceiver(ref, []); + const filledPtr = header(array.name, SCR_ARR_FIELD.filled), + filled = B.tmp(), + covered = B.tmp(), + both = B.tmp(); + B.line(`${filled} = load i32, ptr ${filledPtr}${host.fieldAliasAttachment(filledPtr)}`); + let bound = filled; + if (size === "i64") { + bound = B.tmp(); + B.line(`${bound} = zext i32 ${filled} to i64`); + } + B.line(`${covered} = icmp ule ${size} ${len}, ${bound}`); + B.line(`${both} = and i1 ${valid}, ${covered}`); + valid = both; + } + return valid; +} diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index bb35e70c2..9b11d6240 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -84,6 +84,16 @@ const DYN_HANDLE_TAG_NUM: Record = { child: 16, fileHandle: 17, dir: 44, + cryptoHash: 52, + cryptoHmac: 53, +}; + +/** Handle kinds whose runtime unit boxes them through its own function, + * which installs the tag's ops on first use (scr_crypto.h). Programs that + * never box one of these values do not link its dynamic members. */ +const DYN_HANDLE_BOX_FN: Record = { + cryptoHash: "scr_crypto_hash_to_dyn", + cryptoHmac: "scr_crypto_hash_to_dyn", }; export const DYN_KIND = { @@ -1106,6 +1116,10 @@ export class LlDyn { private readonly jsonSchemas = new Map(); private readonly jsonParsers = new Map(); private readonly recordNameTables = new Map(); + /** Some emitted helper can keep a record's undeclared source: a checked + * record builder that notes it, or a JSON schema of a closed record + * (alloc.ts RECORD_SOURCES_GATE). */ + keepsRecordSources = false; /** Targets the one-pass parser (scr_json_parse_schema) can build * directly: number, boolean and string leaves inside arrays and records @@ -1217,12 +1231,14 @@ export class LlDyn { `llvm emitter bug: JSON schema of unknown shape ${t.shapeId}`, ); const struct = mangleRecordStruct(t.shapeId); - const index = new Map(shape.fields.map((f, i) => [f.name, i + 1])); + const base = host.nullableUnions.fieldBase(t.shapeId); + const index = new Map(shape.fields.map((f, i) => [f.name, i + base])); // The runtime names kept-source keys by this index (record-sources.ts). const order = recordSourceFieldOrder(shape); + if (recordMayKeepSource(shape)) this.keepsRecordSources = true; const fieldTy = `{ ptr, ${S}, ${S}, ptr }`; const entries = order.map((fieldName) => { - const field = shape.fields[index.get(fieldName)! - 1]!; + const field = shape.fields[index.get(fieldName)! - base]!; const sub = this.jsonSchema(field.type); const offset = `ptrtoint (ptr getelementptr (%${struct}, ptr null, i64 0, i32 ${index.get(fieldName)}) to ${S})`; return `${fieldTy} { ptr ${host.cstr(fieldName)}, ${S} ${Buffer.byteLength(fieldName, "utf8")}, ${S} ${offset}, ptr @${sub} }`; @@ -1240,7 +1256,7 @@ export class LlDyn { const overflow = shape.indexValue ? { elem: `@${this.jsonSchema(shape.indexValue)}`, - offset: `ptrtoint (ptr getelementptr (%${struct}, ptr null, i64 0, i32 ${shape.fields.length + 1}) to ${S})`, + offset: `ptrtoint (ptr getelementptr (%${struct}, ptr null, i64 0, i32 ${shape.fields.length + base}) to ${S})`, } : { elem: "null", offset: "0" }; body = `${S} 3, ${S} ${entries.length}, ptr ${fields}, ptr ${overflow.elem}, ptr @${mangleRecordNew(t.shapeId)}, ptr null, ptr ${vAdapters(host, t).release}, ptr null, ${S} ${overflow.offset}`; @@ -1547,18 +1563,19 @@ export class LlDyn { B.line(`call void @scr_arr_outline(ptr ${cached})`); B.line(`call void @scr_arr_outline(ptr ${checked})`); } + const base = t.kind === "record" ? host.nullableUnions.fieldBase(t.shapeId) : 1; const members = t.kind === "record" ? [ ...shape!.fields.map((field, index) => ({ - index: index + 1, + index: index + base, type: llFieldType(field.type, host.inlineUnions.record(field.type)), name: field.name, })), ...(shape!.indexValue ? [ { - index: shape!.fields.length + 1, + index: shape!.fields.length + base, type: "ptr" as const, name: "[key: string] overflow", }, @@ -1819,7 +1836,8 @@ export class LlDyn { `llvm emitter bug: dynCheck of unknown shape ${t.shapeId}`, ); const struct = mangleRecordStruct(t.shapeId); - const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + 1])); + const base = host.nullableUnions.fieldBase(t.shapeId); + const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + base])); const releaseR = (): void => { B.line(`call void ${releaseSym(host, t)}(ptr %r0)`); }; @@ -1978,6 +1996,7 @@ export class LlDyn { // that has them stays beside the record so its reflective // surfaces still observe them (scr_record_source_note). if (recordMayKeepSource(shape)) { + this.keepsRecordSources = true; const names = this.recordNameTable(t.shapeId, shape); host.declare( `declare void @scr_record_source_note(ptr, ptr, ptr, ptr, ${host.sizeType})`, @@ -1996,7 +2015,7 @@ export class LlDyn { const ovfp = B.tmp(); const ovf = B.tmp(); B.line( - `${ovfp} = getelementptr inbounds %${struct}, ptr %r0, i64 0, i32 ${shape.fields.length + 1}`, + `${ovfp} = getelementptr inbounds %${struct}, ptr %r0, i64 0, i32 ${shape.fields.length + base}`, ); B.line(`${ovf} = load ptr, ptr ${ovfp} ; overflow map`); if (shape.fields.length === 0) { @@ -2578,7 +2597,8 @@ export class LlDyn { if (!shape) throw new InternalCompilerError(`llvm emitter bug: to-dyn of unknown shape ${t.shapeId}`); const struct = mangleRecordStruct(t.shapeId); - const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + 1])); + const base = host.nullableUnions.fieldBase(t.shapeId); + const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + base])); const loadFieldOf = (fname: string, ft: IrType): string => { const p = B.tmp(); B.line( @@ -2702,7 +2722,7 @@ export class LlDyn { const ovfp = B.tmp(); const ovf = B.tmp(); B.line( - `${ovfp} = getelementptr inbounds %${struct}, ptr %v, i64 0, i32 ${shape.fields.length + 1}`, + `${ovfp} = getelementptr inbounds %${struct}, ptr %v, i64 0, i32 ${shape.fields.length + base}`, ); B.line(`${ovf} = load ptr, ptr ${ovfp} ; overflow map`); const ks = B.tmp(); @@ -2941,9 +2961,17 @@ export class LlDyn { // tag's installed ops. const h = DYN_HANDLE_KINDS.get(t.kind); if (h) { - host.declare(`declare ptr @scr_dyn_new_handle(ptr, i32)`); + const box = DYN_HANDLE_BOX_FN[t.kind]; const r = B.tmp(); - B.line(`${r} = call ptr @scr_dyn_new_handle(ptr %v, i32 ${DYN_HANDLE_TAG_NUM[t.kind]})`); + if (box) { + host.declare(`declare ptr @${box}(ptr)`); + B.line(`${r} = call ptr @${box}(ptr %v)`); + } else { + host.declare(`declare ptr @scr_dyn_new_handle(ptr, i32)`); + B.line( + `${r} = call ptr @scr_dyn_new_handle(ptr %v, i32 ${DYN_HANDLE_TAG_NUM[t.kind]})`, + ); + } B.terminate(`ret ptr ${r}`); break; } diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index 698a48aaf..efab6eb21 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -2,7 +2,14 @@ import { type ArraySearchKind, emitArraySearchHelpers } from "./array-search.js" import { emitStringInputs } from "./string-lifetimes.js"; import { emitLlvmLayouts } from "./layouts.js"; import { compileRegexMatcher, type RegexMatcherCode } from "./regex-matchers.js"; -import { CYC_BUF, CYC_HDR, FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js"; +import { + CYC_BUF, + CYC_HDR, + FIRST_EMITTED_CYC_KIND, + inlineAllocSupported, + RECORD_SOURCES_GATE, + WEAK_KEYS_GATE, +} from "./alloc.js"; import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js"; import { InternalCompilerError } from "../../errors.js"; import { createHash } from "node:crypto"; @@ -50,11 +57,19 @@ import { VOID, } from "../../ir/ir.js"; import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-features.js"; +import { captureFreeFunctions, observesStacks } from "../stack-observations.js"; import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js"; +import { findUnaliasedLocalArrays } from "../../ir/array-length-stability.js"; +import { + countedDenseShape, + emitDenseLoopGuard, + matchDenseArrayLoop, + type DenseArrayLoop, +} from "./dense-array-loops.js"; import { emitCountedLoopLimit } from "./counted-loops.js"; import { emitSafeIntegerCheck, @@ -151,6 +166,7 @@ import { mangleFnClosure, mangleFunction, mangleGenDrop, + mangleGenSourceClose, mangleGenSpawn, mangleGlobal, mangleLocal, @@ -229,7 +245,10 @@ import { findAppendedArrayLiterals } from "./appended-array-literals.js"; import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js"; import { findScalarStringSlices, + findScratchStringSlices, + findLentStringParameters, emitStringSliceSnapshot, + type LentStringParameters, type StringSliceSnapshot, } from "./string-slices.js"; import { LazyCaptures } from "./lazy-captures.js"; @@ -383,6 +402,7 @@ import { elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, + emitShapeUnionHelpers, FN_ATTRS, inlineRcDecls, type InlineRcMode, @@ -623,6 +643,10 @@ export class LlEmitter { private loopArrayBorrows: ReadonlySet = new Set(); private scalarStringSlices = new Map(); readonly stringSlices = new Map(); + /** Slices written into stack buffers (findScratchStringSlices). */ + scratchStringSlices: ReadonlySet = new Set(); + /** String parameters a caller may lend scratch slices to. */ + private lentStringParameters: LentStringParameters = new Map(); private localStackUnions = new Map(); private localStackObjects = new Map(); private reassignedStackObjects = new Set(); @@ -674,6 +698,21 @@ export class LlEmitter { return this.sizeType === "i64" ? ", !range !{i64 0, i64 4294967296}" : ""; } + /** Whether an element access at `index` of `arr` lies in the hole-free + * prefix: the counter of a dense array loop's fast copy indexing one of + * the arrays its entry test covered. */ + provenDenseIndex(arr: IrExpr, index: IrExpr): boolean { + const loop = this.denseLoop; + return ( + loop !== null && + arr.kind === "varRef" && + loop.arrays.has(arr.localId) && + index.kind === "varRef" && + index.localId === loop.counter && + this.integerLoopBindings.has(loop.counter) + ); + } + fieldAliasAttachment(ptr: string): string { const tag = this.fieldPointerTags.get(ptr); return tag === undefined ? "" : `, !tbaa !${tag}`; @@ -1030,6 +1069,14 @@ export class LlEmitter { * immortal from construction (classes.ts programLifetimeClass). */ readonly programLifetime: ReadonlySet; private numericLocals = new Map(); + /** Local arrays of the current function that no other binding can hold + * (ir/array-length-stability.ts). */ + private unaliasedArrays: ReadonlySet = new Set(); + /** Dense array loops (dense-array-loops.ts) run in this function. */ + private denseLoopsEnabled = false; + private denseLoopEntries: ReturnType = new Map(); + /** The fast copy of a dense array loop being emitted. */ + private denseLoop: DenseArrayLoop | null = null; private captureIds = new Set(); /** Active induction bindings retain their width independently of size_t. */ integerLoopBindings = new Map< @@ -1134,19 +1181,15 @@ export class LlEmitter { readonly chainSlots = new Map(); logArgSlots = 0; private readonly stackTraces: boolean; + /** Stack-trace mode: functions no capture can include (no frame push). */ + private readonly unframedFunctions: ReadonlySet; constructor( readonly mod: IrModule, options: LlvmTargetOptions, ) { - this.stackTraces = mod.functions.some( - (fn) => - !everyStmtList(fn.body, { - stmt: () => true, - expr: (expr) => !(expr.kind === "libCall" && expr.fn === "error.stack"), - }), - ); - this.nullableUnions = new NullableUnions(mod); + this.stackTraces = observesStacks(mod); + this.nullableUnions = new NullableUnions(mod, options.debugSources === undefined); this.mockTimers = moduleUsesMockTimers(mod); this.debug = options.debugSources === undefined @@ -1227,6 +1270,9 @@ export class LlEmitter { // which Windows lacks. this.stacklessAsyncAllowed = this.stacklessGenerators && mod.lib === undefined && !windows; this.publishedTypes = computePublishedTypes(mod); + this.unframedFunctions = this.stackTraces + ? captureFreeFunctions(mod, this.stackChecks, this.publishedTypes) + : new Set(); this.immutableFields = computeImmutableFields(mod); this.constructorFunctions = new Set((mod.classes ?? []).map((c) => `%${c.name}.constructor`)); this.indirectMayThrow = mt.indirect || mod.workers === true; @@ -1245,8 +1291,10 @@ export class LlEmitter { // Optional array reads also cover nullable unions: emitLocalArrayRead // then yields the (borrowed or owned) element pointer, not a stack box. // Arrays of such a union are read the same way. - this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById, (id) => - this.nullableUnions.has(id), + this.optionalArrayReads = new OptionalArrayReads( + this.fnByName, + this.unionsById, + (id) => this.nullableUnions.single(id) !== null, ); this.unionSources = { unions: this.boxedUnionsById, @@ -1299,7 +1347,8 @@ export class LlEmitter { (type) => this.plainReference(type) && type.kind !== "string" && - (type.kind !== "union" || this.nullableUnions.get(type.unionId)?.arm.kind !== "string"), + (type.kind !== "union" || + this.nullableUnions.single(type.unionId)?.arm.kind !== "string"), this.walkBorrowsByFunction.get(fn.name) ?? new Set(), ); if (rebound.size === 0) continue; @@ -1325,6 +1374,8 @@ export class LlEmitter { return shape !== undefined && !recordDynKeepsIdentity(shape); }, ); + this.lentStringParameters = + this.debug === null ? findLentStringParameters(this.fnByName, this.callLifetimes) : new Map(); this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes); // Integer clones signal a stack overflow through their result, which // needs the single-threaded guard; worker programs also poll for @@ -1350,6 +1401,27 @@ export class LlEmitter { const traced = computeTraced(mod); this.tracedShapes = traced.shapes; this.tracedUnions = traced.unions; + // A shape union's value is whichever record it holds, and the collector + // visits it as that record: when one arm carries a collector header, + // every arm must (nullable-unions.ts). Unions that share an arm pass a + // header on to each other's arms, so repeat until no union adds one. + const shapeUnions = this.nullableUnions.allShapeUnions(); + for (let changed = true; changed;) { + changed = false; + for (const union of shapeUnions) { + if ( + !this.tracedUnions.has(union.unionId) && + !union.arms.some((arm) => this.tracedShapes.has(`record:${arm.shapeId}`)) + ) + continue; + for (const arm of union.arms) { + const key = `record:${arm.shapeId}`; + if (this.tracedShapes.has(key)) continue; + this.tracedShapes.add(key); + changed = true; + } + } + } this.shapeHost = { sizeType: this.sizeType, cycleBufOffset: this.cycleBufOffset, @@ -1900,7 +1972,10 @@ export class LlEmitter { } emitParts(): string[] { - const parts = limitInlineDynReleases(this.emitModuleParts()); + const parts = [ + ...limitInlineDynReleases(this.emitModuleParts()), + ...this.disposeObserverGates(), + ]; if (!this.executableTls) return parts; // A worker executable's thread-locals all live in the executable's own // TLS block: the program's internal ones, and the runtime's external @@ -1923,6 +1998,21 @@ export class LlEmitter { ); } + /** The dispose-observer gates the emitted frees and record writers test + * (alloc.ts): defined after every helper is emitted, because dynCheck + * builders, JSON schemas and weak collection constructors are requested + * lazily. */ + private disposeObserverGates(): string[] { + const weak = [...this.decls].some( + (d) => d.includes("@scr_weak_map_new(") || d.includes("@scr_weak_set_new("), + ); + return [ + `${WEAK_KEYS_GATE} = internal constant i1 ${weak}`, + `${RECORD_SOURCES_GATE} = internal constant i1 ${this.dyn.keepsRecordSources}`, + ``, + ]; + } + private emitModuleParts(): string[] { // Function bodies first (the literal/unit/fn-value tables fill as they // emit), then the file assembles around them — the runtime ABI’s order. @@ -3690,6 +3780,7 @@ export class LlEmitter { return [ ...late, ...emitNullableRetainWrappers(this.shapeHost), + ...emitShapeUnionHelpers(this.shapeHost), ...emitInlineRcHelpers(this.shapeHost), ]; } @@ -4533,6 +4624,25 @@ export class LlEmitter { dr.push(` call void @free(ptr %ap)`, ` ret void`, `}`, ``); for (const line of dr) out.push(line); + // 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 (closesSource) { + const field = lifted ? 1 : 0; + this.declare(`declare void @scr_gen_close(ptr)`); + this.declare(`declare void @scr_gen_set_unstarted_return(ptr, ptr)`); + out.push( + `define internal void @${mangleGenSourceClose(fn.name)}(ptr %ap) ${FN_ATTRS} {`, + `entry:`, + ` %sp = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${field}`, + ` %sv = load ptr, ptr %sp`, + ` call void @scr_gen_close(ptr %sv)`, + ` ret void`, + `}`, + ``, + ); + } + // Spawn wrapper: pack the args (+1 moves in), allocate the // SUSPENDED fiber — nothing runs until the first .next(). if (lifted) { @@ -4549,6 +4659,11 @@ 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})`, + ...(closesSource + ? [ + ` call void @scr_gen_set_unstarted_return(ptr %gg, ptr @${mangleGenSourceClose(fn.name)})`, + ] + : []), ` ret ptr %gg`, `}`, ``, @@ -5857,7 +5972,11 @@ export class LlEmitter { const nullable = this.nullableUnions.get(unionId); if (nullable) { // The owned payload IS the nullable union's value. - if (tag !== nullable.refTag) + if ( + nullable.repr === "single" + ? tag !== nullable.refTag + : !nullable.arms.some((arm) => arm.tag === tag) + ) throw new InternalCompilerError( `llvm emitter bug: payload wrap into unit arm ${tag} of ${unionId}`, ); @@ -5962,7 +6081,7 @@ export class LlEmitter { const index = shape.fields.findIndex((entry) => entry.name === field); if (index < 0) throw new InternalCompilerError(`llvm emitter bug: missing union field ${field}`); - layout = "record:"; + layout = this.nullableUnions.fieldBase(arm.shapeId) === 2 ? "record:tagged;" : "record:"; for (let i = 0; i <= index; i++) { const type = shape.fields[i]!.type; layout += `${llFieldType(type, this.inlineUnions.record(type))};`; @@ -5998,7 +6117,7 @@ export class LlEmitter { ); const p = this.B.tmp(); this.B.line( - `${p} = getelementptr inbounds %${mangleRecordStruct(shapeId)}, ptr ${objName}, i64 0, i32 ${idx + 1}`, + `${p} = getelementptr inbounds %${mangleRecordStruct(shapeId)}, ptr ${objName}, i64 0, i32 ${idx + this.nullableUnions.fieldBase(shapeId)}`, ); const type = shape.fields[idx]!.type; return { ptr: p, type, inline: this.inlineUnions.record(type) }; @@ -6012,7 +6131,7 @@ export class LlEmitter { const p = this.B.tmp(); const v = this.B.tmp(); this.B.line( - `${p} = getelementptr inbounds %${mangleRecordStruct(shapeId)}, ptr ${objName}, i64 0, i32 ${shape.fields.length + 1}`, + `${p} = getelementptr inbounds %${mangleRecordStruct(shapeId)}, ptr ${objName}, i64 0, i32 ${shape.fields.length + this.nullableUnions.fieldBase(shapeId)}`, ); this.B.line(`${v} = load ptr, ptr ${p}`); return v; @@ -6675,6 +6794,7 @@ export class LlEmitter { const initializerBindings = this.initializerBindings.get(fn.name) ?? []; const numericFn = withInitializerBindings(fn, initializerBindings); this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l])); + this.unaliasedArrays = findUnaliasedLocalArrays(fn); this.borrowedParameters.clear(); this.referenceWrites?.enter(fn); this.reboundOwnerSlots.clear(); @@ -6713,6 +6833,12 @@ export class LlEmitter { this.finalFieldBorrows = !fn.async && !fn.generator && constructorClass(fn.name) === null && this.finalFields.size > 0; this.byteWindowsEnabled = this.countedLoopsEnabled; + this.denseLoopsEnabled = this.countedLoopsEnabled; + this.denseLoop = null; + this.denseLoopEntries = + this.denseLoopsEnabled && numericFn.locals.some((l) => l.type.kind === "array") + ? findInitializedByteLoopBindings(fn) + : new Map(); this.byteWindowEntries = this.byteWindowsEnabled && numericFn.locals.some((l) => l.type.kind === "bytes") ? findInitializedByteLoopBindings(fn) @@ -6750,6 +6876,15 @@ export class LlEmitter { ); this.scalarStringSlices = this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map(); + this.scratchStringSlices = + this.debug === null + ? findScratchStringSlices( + fn, + this.callLifetimes, + this.scalarStringSlices, + this.lentStringParameters, + ) + : new Set(); this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes); this.readNumberMapWrites = findReadNumberMapWrites(fn); this.mapEntryFusion = this.debug === null ? findMapEntryFusions(fn) : null; @@ -7040,13 +7175,39 @@ export class LlEmitter { // Stackful native fibers and native coroutine frames retain these frames // while suspended. The active exception context owns the chain, so // concurrent fibers isolate it. - if (this.stackTraces && !this.wasi && fn.sourceName !== undefined) { - this.declare(`declare void @scr_stack_enter(ptr, ptr)`); - this.declare(`declare void @scr_stack_leave(ptr)`); + if ( + this.stackTraces && + !this.wasi && + fn.sourceName !== undefined && + !this.unframedFunctions.has(fn.name) + ) { B.entryAllocas.push(`%source_frame = alloca { ptr, ptr }`); - const frame = ` at ${fn.sourceName} (${fn.loc.file})`; - B.line(`call void @scr_stack_enter(ptr %source_frame, ptr ${this.cstr(frame)})`); - B.returnEpilogue = `call void @scr_stack_leave(ptr %source_frame)`; + const frame = this.cstr(` at ${fn.sourceName} (${fn.loc.file})`); + if ( + B.excCellInvariant && + this.mod.lib === undefined && + this.mod.workers !== true && + this.sizeType === "i64" + ) { + // Executables link the active-cell pointer as a plain global, and a + // synchronous body sees one cell from entry to every return: push + // and pop the frame inline (ScrExcCell.stack is at byte 56 on + // 64-bit targets) instead of calling the runtime twice per call. + this.declare(`@scr_exc_active = external global ptr`); + B.line(`%source_frame.cell = load ptr, ptr @scr_exc_active`); + B.line(`%source_frame.chain = getelementptr inbounds i8, ptr %source_frame.cell, i64 56`); + B.line(`%source_frame.previous = load ptr, ptr %source_frame.chain`); + B.line(`store ptr %source_frame.previous, ptr %source_frame`); + B.line(`%source_frame.text = getelementptr inbounds i8, ptr %source_frame, i64 8`); + B.line(`store ptr ${frame}, ptr %source_frame.text`); + B.line(`store ptr %source_frame, ptr %source_frame.chain`); + B.returnEpilogue = `store ptr %source_frame.previous, ptr %source_frame.chain`; + } else { + this.declare(`declare void @scr_stack_enter(ptr, ptr)`); + this.declare(`declare void @scr_stack_leave(ptr)`); + B.line(`call void @scr_stack_enter(ptr %source_frame, ptr ${frame})`); + B.returnEpilogue = `call void @scr_stack_leave(ptr %source_frame)`; + } } if (this.stackChecks?.has(fn.name)) this.emitStackCheck(); if (this.workerEntryPolls?.has(fn.name)) this.emitPendingCheck(undefined, true); @@ -8185,7 +8346,16 @@ export class LlEmitter { const acc = elemAccess(s.arr.type.elem); this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name); if (acc === "ref") this.moveTemp(v); - emitDenseArraySet(this, arr.name, idx, s.index, acc, s.arr.type.elem, v.name); + emitDenseArraySet( + this, + arr.name, + idx, + s.index, + acc, + s.arr.type.elem, + v.name, + this.provenDenseIndex(s.arr, s.index), + ); break; } case "arraySetLength": { @@ -8632,7 +8802,12 @@ export class LlEmitter { (this.debug === null ? matchIntegerBytesForLoop(s, this.numericLocals) : null); const countedLoop = !integerLoop && this.countedLoopsEnabled - ? matchIntegerCountedForLoop(s, this.numericLocals, this.integerRanges) + ? matchIntegerCountedForLoop( + s, + this.numericLocals, + this.integerRanges, + this.unaliasedArrays, + ) : null; const window = countedLoop && this.byteWindowsEnabled @@ -8645,6 +8820,49 @@ export class LlEmitter { this.byteWindowEntries.get(s) ?? new Set(), ) : null; + const denseShape = !this.denseLoopsEnabled + ? null + : integerArrayLoop + ? { + counter: integerArrayLoop.localId, + length: integerArrayLoop.limitReceiver as IrExpr & { kind: "varRef" }, + range: { min: 0, max: 4294967294 }, + } + : countedLoop && !countedLoop.guarded + ? countedDenseShape(s, countedLoop) + : null; + const dense = + denseShape && !window + ? matchDenseArrayLoop( + s, + denseShape, + this.numericLocals, + this.unaliasedArrays, + this.denseLoopEntries.get(s) ?? new Set(), + ) + : null; + if (dense) { + const valid = emitDenseLoopGuard(this, dense); + const fast = B.newLabel("dense.loop.fast"), + slow = B.newLabel("dense.loop.slow"), + done = B.newLabel("dense.loop.done"); + B.condBr(valid, fast, slow); + this.denseLoopsEnabled = false; + try { + B.startBlock(slow); + this.emitStmt(s); + B.br(done); + B.startBlock(fast); + this.denseLoop = dense; + this.emitStmt(s); + B.br(done); + } finally { + this.denseLoop = null; + this.denseLoopsEnabled = true; + } + B.startBlock(done); + break; + } if (window) { const valid = emitByteWindowGuard(this, window); const fast = B.newLabel("bytes.window.fast"), @@ -8747,7 +8965,10 @@ export class LlEmitter { this.integerLoopBindings.set(integerLoop.localId, { slot: integerSlot, type: this.sizeType, - range: { min: 0, max: integerArrayLoop ? 4294967294 : Number.MAX_SAFE_INTEGER - 1 }, + range: + this.denseLoop?.counter === integerLoop.localId + ? this.denseLoop.range + : { min: 0, max: integerArrayLoop ? 4294967294 : Number.MAX_SAFE_INTEGER - 1 }, }); } else if (countedLoop) { integerSlot = B.slot(); @@ -8762,7 +8983,10 @@ export class LlEmitter { slot: integerSlot, type: "i64", signed: true, - range: countedLoop.range, + range: + this.denseLoop?.counter === countedLoop.localId + ? this.denseLoop.range + : countedLoop.range, }); countedLimit = emitCountedLoopLimit(this, countedLoop); } else if (s.init) { @@ -9344,14 +9568,15 @@ export class LlEmitter { B.line(`${stash} = call ptr @scr_exc_take() ; stash across finally`); B.line(`store ptr ${stash}, ptr ${stashSlot}`); this.scopes.push([{ slot: stashSlot, type: CAUGHT }]); - const suppressHandler = s.suppressFinallyErrors - ? { - label: B.newLabel("try.fs"), - used: false, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - } - : null; + const suppressHandler = + s.suppressFinallyErrors || s.dropFinallyErrors + ? { + label: B.newLabel("try.fs"), + used: false, + frameDepth: this.frames.length, + scopeDepth: this.scopes.length, + } + : null; if (suppressHandler) this.tryStack.push(suppressHandler); this.emitBlock(s.finallyBody!); if (suppressHandler) this.tryStack.pop(); @@ -9361,8 +9586,9 @@ export class LlEmitter { this.emitUnwind(); if (suppressHandler?.used) { B.startBlock(suppressHandler.label); - this.declare(`declare void @scr_exc_suppress(ptr)`); - B.line(`call void @scr_exc_suppress(ptr ${stash}) ; consumes stash`); + const cleanup = s.dropFinallyErrors ? "scr_exc_keep_first" : "scr_exc_suppress"; + this.declare(`declare void @${cleanup}(ptr)`); + B.line(`call void @${cleanup}(ptr ${stash}) ; consumes stash`); this.emitUnwind(); } } @@ -10105,7 +10331,12 @@ export class LlEmitter { unionsById: this.unionsById, recordsById: this.recordsById, inlineUnions: this.inlineUnions, - nullableArm: (t) => this.nullableUnions.of(t)?.arm ?? null, + nullableArm: (t) => { + const nullable = this.nullableUnions.of(t); + return nullable?.repr === "single" ? nullable.arm : null; + }, + shapeUnion: (t) => this.nullableUnions.of(t)?.repr === "shapes", + fieldBase: (shapeId) => this.nullableUnions.fieldBase(shapeId), classInfo: (className) => { const meta = this.classMetaOf(className); return { @@ -10826,7 +11057,11 @@ export class LlEmitter { const nullable = this.nullableUnions.get(e.unionId); if (!nullable) return null; if (isUnitType(e.value.type)) return e.value.kind === "unitLit" ? "unit" : null; - return e.tag === nullable.refTag && isRefCounted(e.value.type) ? "ref" : null; + const refArm = + nullable.repr === "single" + ? e.tag === nullable.refTag + : nullable.arms.some((arm) => arm.tag === e.tag); + return refArm && isRefCounted(e.value.type) ? "ref" : null; } materializeSplitLocal(localId: string): void { diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index e5560f14f..d71df333a 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -182,6 +182,7 @@ export function genResultThunkFor( sym = mangleGenResThunk(host.resolveThunks.size); host.resolveThunks.set(key, sym); const shape = host.recordsById.get(recT.shapeId); + const base = host.nullableUnions.fieldBase(recT.shapeId); const valueT = shape?.fields.find((f) => f.name === "value")?.type; if (!shape || !valueT) throw new InternalCompilerError("llvm emitter bug: genResume record lacks its value field"); @@ -242,7 +243,7 @@ export function genResultThunkFor( ` %r = call ptr @${mangleRecordNew(recT.shapeId)}()`, ` %d = call zeroext i1 @scr_gen_done(ptr %g)`, ` %dz = zext i1 %d to i8`, - ` %dp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${doneIdxD + 1}`, + ` %dp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${doneIdxD + base}`, ` store i8 %dz, ptr %dp`, ` %has = call zeroext i1 @scr_gen_out_has(ptr %g)`, ` br i1 %has, label %take, label %undefv`, @@ -259,7 +260,7 @@ export function genResultThunkFor( ` br label %join`, `join:`, ` %v = load ptr, ptr %vslot`, - ` %vp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${valueIdxD + 1}`, + ` %vp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${valueIdxD + base}`, ` store ptr %v, ptr %vp`, ` ret ptr %r`, `}`, @@ -293,7 +294,7 @@ export function genResultThunkFor( ` %r = call ptr @${mangleRecordNew(recT.shapeId)}()`, ` %d = call zeroext i1 @scr_gen_done(ptr %g)`, ` %dz = zext i1 %d to i8`, - ` %dp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${doneIdx + 1}`, + ` %dp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${doneIdx + base}`, ` store i8 %dz, ptr %dp`, ` br i1 %d, label %doneb, label %susp`, ]; @@ -410,7 +411,7 @@ export function genResultThunkFor( ` br label %join`, `join:`, ` %v = load ptr, ptr %vslot`, - ` %vp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${valueIdx + 1}`, + ` %vp = getelementptr inbounds %${mangleRecordStruct(recT.shapeId)}, ptr %r, i64 0, i32 ${valueIdx + base}`, // An inline value field copies the box's bits and drops the box. ...(host.inlineUnions.record(valueT) ? [ @@ -1092,7 +1093,8 @@ export function settledStoreFor( return { tag, arm }; }; const fieldIndex = (arm: IrType & { kind: "record" }, field: string): number => - host.recordsById.get(arm.shapeId)!.fields.findIndex((f) => f.name === field) + 1; + host.recordsById.get(arm.shapeId)!.fields.findIndex((f) => f.name === field) + + host.nullableUnions.fieldBase(arm.shapeId); const fulfilled = armOf("value"); const rejected = armOf("reason"); const valueT = host.recordsById diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index 9c77690f8..22a0b4df5 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -42,7 +42,7 @@ import { } from "./map-number-lookup.js"; import { emitBorrowedInput } from "./borrowed-inputs.js"; import { emitFusedMapSet, rememberMapEntry } from "./map-entry-fusion.js"; -import { emitStringSliceRead } from "./string-slices.js"; +import { emitScratchStringSlice, emitStringSliceRead } from "./string-slices.js"; import { STRING_CHAR_CODE_AT_DECL, STRING_CHAR_CODE_AT_INT_MARKER, @@ -395,6 +395,7 @@ export function emitStrIntrinsic( false, ); case "slice": + if (host.scratchStringSlices.has(e)) return emitScratchStringSlice(host, e, r, args); return call( "scr_str_slice", "ptr (ptr, double, double)", @@ -403,6 +404,7 @@ export function emitStrIntrinsic( true, ); case "substring": + if (host.scratchStringSlices.has(e)) return emitScratchStringSlice(host, e, r, args); return call( "scr_str_substring", "ptr (ptr, double, double)", @@ -575,7 +577,16 @@ export function emitArrIntrinsic( ? host.constantNumericTables.get(e.receiver.localId) : undefined; if (!table && elem.kind === "f64") - return { name: emitDenseArrayNumber(host, r.name, index, e.args[0]), type: e.type }; + return { + name: emitDenseArrayNumber( + host, + r.name, + index, + e.args[0], + host.provenDenseIndex(e.receiver, e.args[0]!), + ), + type: e.type, + }; host.declare(`declare double @scr_arr_get_number(ptr, double)`); const t = B.tmp(); B.line( diff --git a/packages/compiler/src/backend/llvm/expr-control.ts b/packages/compiler/src/backend/llvm/expr-control.ts index 298ea0199..fd4bc8787 100644 --- a/packages/compiler/src/backend/llvm/expr-control.ts +++ b/packages/compiler/src/backend/llvm/expr-control.ts @@ -330,7 +330,7 @@ export function emitControlExpr( ); B.line(`store ${ty} ${t}, ptr ${slot}`); if (host.mayThrow.has(e.retag)) host.emitPendingCheck(); - } else if (host.nullableUnions.has(e.left.type.unionId)) { + } else if (host.nullableUnions.single(e.left.type.unionId)) { // The owned nullable pointer IS the owned arm value. B.line(`store ${ty} ${l.name}, ptr ${slot}`); } else { @@ -440,7 +440,7 @@ export function emitControlExpr( host.emitBranchInto(slot, e.right); B.br(lj); B.startBlock(lv); - if (typeEquals(e.type, e.left.type) || host.nullableUnions.has(def.id)) { + if (typeEquals(e.type, e.left.type) || host.nullableUnions.single(def.id)) { // Pass-through, or a nullable pointer that IS its owned arm value. B.line(`store ${ty} ${l.name}, ptr ${slot}`); } else { diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index aaae4c35c..e237f8f13 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -64,16 +64,25 @@ export function emitLiteralExpr( const integer = B.tmp(); const number = B.tmp(); B.line(`${integer} = load ${integerSlot.type}, ptr ${integerSlot.slot}`); - // A 64-bit induction value is a safe integer, so its sign bit is - // clear and the signed conversion is exact: one cvtsi2sd instead of - // x86-64's multi-instruction unsigned 64-bit conversion. - const signedConversion = integerSlot.signed === true || integerSlot.type === "i64"; - B.line( - `${number} = ${signedConversion ? "sitofp" : "uitofp"} ${integerSlot.type} ${integer} to double`, - ); const uint32 = integerSlot.type === "i32" ? integer : B.tmp(); if (integerSlot.type !== "i32") B.line(`${uint32} = trunc ${integerSlot.type} ${integer} to i32`); + // An int32 counter (a dense array loop's) converts from its low half, + // so LLVM folds a later `fptosi` of the double (an inlined callee's + // index) back to the integer. Otherwise a 64-bit induction value is + // a safe integer, so its sign bit is clear and the signed conversion + // is exact: one cvtsi2sd instead of x86-64's multi-instruction + // unsigned 64-bit conversion. (A uint32 counter would fold through + // `uitofp i32` too, but the zero extension it adds on x86-64 cost + // more in loops that never convert the double than it saved.) + const range = integerSlot.range; + const signedConversion = integerSlot.signed === true || integerSlot.type === "i64"; + if (range && range.min >= -2147483648 && range.max <= 2147483647) + B.line(`${number} = sitofp i32 ${uint32} to double`); + else + B.line( + `${number} = ${signedConversion ? "sitofp" : "uitofp"} ${integerSlot.type} ${integer} to double`, + ); return { name: number, type: e.type, @@ -851,6 +860,7 @@ export function emitContainerExpr( elem, `scr_arr_get_${acc}`, true, + host.provenDenseIndex(e.arr, e.index), ); return host.own({ name: t, type: e.type }); } diff --git a/packages/compiler/src/backend/llvm/expr-records.ts b/packages/compiler/src/backend/llvm/expr-records.ts index fc9df6daf..3318ae774 100644 --- a/packages/compiler/src/backend/llvm/expr-records.ts +++ b/packages/compiler/src/backend/llvm/expr-records.ts @@ -62,9 +62,17 @@ export function emitUnionRetype( const from = host.nullableUnions.get(e.fromId); const to = host.nullableUnions.get(e.unionId); if (from && to) { - // Equal encodings make the pointer itself the target value. - if (from.refTag === to.refTag && from.unitTag === to.unitTag && from.nullTag === to.nullTag) - return host.own({ name: value.name, type: e.type }); + // Equal encodings make the pointer itself the target value. A shape + // union derives its record arms from the record, so only the unit + // encodings must agree. + const same = + from.repr === "single" && to.repr === "single" + ? from.refTag === to.refTag && from.unitTag === to.unitTag && from.nullTag === to.nullTag + : from.repr === "shapes" && + to.repr === "shapes" && + from.unitTag === to.unitTag && + from.nullTag === to.nullTag; + if (same) return host.own({ name: value.name, type: e.type }); return host.own({ name: rebuildRetype(host, value.name, e), type: e.type }); } if (from || to) return host.own({ name: rebuildRetype(host, value.name, e), type: e.type }); diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 55fa363cd..76ef35358 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -500,16 +500,17 @@ export function streamTypedRefCommitAdapter( ` br i1 %missing, label %done, label %swap`, `swap:`, ]; + const base = host.nullableUnions.fieldBase(t.shapeId); const members = [ ...shape.fields.map((field, index) => ({ - index: index + 1, + index: index + base, type: llFieldType(field.type, host.inlineUnions.record(field.type)), name: field.name, })), ...(shape.indexValue ? [ { - index: shape.fields.length + 1, + index: shape.fields.length + base, type: "ptr" as const, name: "[key: string] overflow", }, @@ -943,7 +944,7 @@ export function streamTypedRefMaterializeAdapter( B.line(`${out} = call ptr @scr_dyn_new_arr()`); const fields = [...shape.fields].sort((a, b) => Number(a.name) - Number(b.name)); for (const field of fields) { - const index = shape.fields.indexOf(field) + 1; + const index = shape.fields.indexOf(field) + host.nullableUnions.fieldBase(t.shapeId); const fieldPtr = B.tmp(); B.line( `${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`, @@ -980,7 +981,7 @@ export function streamTypedRefMaterializeAdapter( liveRecordOps(host, t, snapshot, fields); } for (const field of fields) { - const index = shape.fields.indexOf(field) + 1; + const index = shape.fields.indexOf(field) + host.nullableUnions.fieldBase(t.shapeId); const fieldPtr = B.tmp(); B.line( `${fieldPtr} = getelementptr inbounds %${mangleRecordStruct(t.shapeId)}, ptr %p, i64 0, i32 ${index}`, @@ -1202,7 +1203,7 @@ function liveRecordOps( const names = `${snapshot}_names`; const ops = `${snapshot}_rops`; const loadField = (B: BlockBuilder, field: IrRecordShape["fields"][number]): string => { - const index = shape.fields.indexOf(field) + 1; + const index = shape.fields.indexOf(field) + host.nullableUnions.fieldBase(t.shapeId); const fieldPtr = B.tmp(); B.line(`${fieldPtr} = getelementptr inbounds %${struct}, ptr %p, i64 0, i32 ${index}`); return loadStructField( diff --git a/packages/compiler/src/backend/llvm/lib-datagram.ts b/packages/compiler/src/backend/llvm/lib-datagram.ts index c97744f64..3fae845e2 100644 --- a/packages/compiler/src/backend/llvm/lib-datagram.ts +++ b/packages/compiler/src/backend/llvm/lib-datagram.ts @@ -29,7 +29,7 @@ function messageAdapter(host: LlvmEmitterContext, type: IrType): string { const isString = field === "address" || field === "family"; if (isString) lines.push(` %${field}.owned = call ptr @scr_str_retain_v(ptr %${field})`); lines.push( - ` %${field}.slot = getelementptr inbounds %${mangleRecordStruct(type.shapeId)}, ptr %record, i64 0, i32 ${index + 1}`, + ` %${field}.slot = getelementptr inbounds %${mangleRecordStruct(type.shapeId)}, ptr %record, i64 0, i32 ${index + host.nullableUnions.fieldBase(type.shapeId)}`, ` store ${isString ? "ptr" : "double"} %${field}${isString ? ".owned" : ""}, ptr %${field}.slot`, ); } @@ -169,7 +169,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L ); const slot = host.B.tmp(); host.B.line( - `${slot} = getelementptr inbounds %${mangleRecordStruct(type.shapeId)}, ptr ${record}, i64 0, i32 ${index + 1}`, + `${slot} = getelementptr inbounds %${mangleRecordStruct(type.shapeId)}, ptr ${record}, i64 0, i32 ${index + host.nullableUnions.fieldBase(type.shapeId)}`, ); host.B.line(`store ${ty} ${value}, ptr ${slot}`); } diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index fdfe723d2..f21ded106 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -9,6 +9,7 @@ import { emitTestLibCall } from "./lib-test.js"; import { InternalCompilerError } from "../../errors.js"; import { MAY_THROW_LIB_FNS } from "../../ir/builtin-effects.js"; +import type { IrExpr, IrType } from "../../ir/ir.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import type { LlvmEmitterContext, LibCallExpr, LibCallPrefix, LlValue } from "./expr-context.js"; import { @@ -21,6 +22,7 @@ import { isDynUndefined } from "../../ir/runtime-features.js"; import { DYN_KIND } from "./dyn.js"; import { rawBytes } from "./lib-abi.js"; import { emitInlineAtomicsOp } from "./expr-bytes.js"; +import { regexLiteralCaptures } from "./regex-matchers.js"; export function emitAssertInspectLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { const B = host.B; @@ -642,8 +644,66 @@ function emitAssertFailing( * `a.name.localeCompare(b.name)` reads both fields without retaining). */ const READ_ONLY_STRING_LIB_FNS: ReadonlySet = new Set(["str.localeCompare"]); +/** The parameter count of a replace callback the runtime can call through + * the closure ABI (scr_regex_replace_fn), or null when it needs the dyn + * box. Each declared parameter must receive a value of its declared kind + * in every match: the match string at 0; with more than one parameter the + * pattern must be a literal whose captures are known, so position i is a + * capture that every match sets (a string), the offset (a number) right + * after the captures, or the subject (a string) after that. */ +function typedReplacerArity(callback: IrType, pattern: IrExpr): number | null { + if (callback.kind !== "func" || callback.rest || callback.argumentsAll) return null; + if (callback.ret.kind !== "string") return null; + const params = callback.params; + if (params.length > 4) return null; + if (params.length === 0) return 0; + if (params[0]!.kind !== "string") return null; + if (params.length === 1) return 1; + if (pattern.kind !== "regexLit") return null; + const captures = regexLiteralCaptures(pattern.pattern, pattern.flags); + if (!captures) return null; + for (let i = 1; i < params.length; i++) { + const kind = params[i]!.kind; + const ok = + i < captures.count + ? kind === "string" && captures.always[i] === true + : i === captures.count + ? kind === "f64" + : i === captures.count + 1 && kind === "string"; + if (!ok) return null; + } + return params.length; +} + +/** `s.replace(re, fn)` / `replaceAll` with a typed callback the runtime can + * call directly: the dyn box the IR wraps it in is never observable (the + * runtime only calls it, with `this` undefined), so the closure goes to + * scr_regex_replace_fn as it is. Null keeps the generic path. */ +function emitTypedRegexReplace(host: LlvmEmitterContext, e: LibCallExpr): LlValue | null { + const [subject, pattern, callback, all] = e.args; + if (!subject || !pattern || callback?.kind !== "dynFrom" || all?.kind !== "boolLit") return null; + const arity = typedReplacerArity(callback.value.type, pattern); + if (arity === null) return null; + const B = host.B; + const s = host.emitExpr(subject); + const re = host.emitExpr(pattern); + const fn = host.emitExpr(callback.value); + host.declare(`declare ptr @scr_regex_replace_fn(ptr, ptr, ptr, i32, i1 zeroext)`); + const result = B.tmp(); + B.line( + `${result} = call ptr @scr_regex_replace_fn(ptr ${s.name}, ptr ${re.name}, ptr ${fn.name}, i32 ${arity}, i1 zeroext ${all.value})`, + ); + const output = host.own({ name: result, type: e.type }); + host.emitPendingCheck(); + return output; +} + export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { if (USES_TIMERS_LIB_FNS.has(e.fn)) host.usesTimers = true; + if (e.fn === "regex.replaceCallback") { + const typed = emitTypedRegexReplace(host, e); + if (typed) return typed; + } if (e.fn === "process.nextTick") return host.emitAsyncContextLibCall(e); if ( e.fn === "sp.pipeline" || @@ -774,6 +834,8 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { case "blob": case "record": case "disposable": + case "punycode": + case "constants": return host.emitGenericLibCall(e); case "test": return emitTestLibCall(host, e); diff --git a/packages/compiler/src/backend/llvm/lib-filesystem.ts b/packages/compiler/src/backend/llvm/lib-filesystem.ts index 0e7d02a15..74278789c 100644 --- a/packages/compiler/src/backend/llvm/lib-filesystem.ts +++ b/packages/compiler/src/backend/llvm/lib-filesystem.ts @@ -912,11 +912,8 @@ export function emitPathUrlLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll scopeid.inline, ); } - const rc = vAdapters(host.shapeHost, t); - const rowU = B.tmp(); - B.line( - `${rowU} = call ptr @scr_union_new_ref(i32 ${tag}, ptr ${r}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, t)})`, - ); + // The row (+1) moves into its union arm (a shape union is the row). + const rowU = host.unionNewOwned(infoT.unionId, tag, { name: r, type: t }); B.line(`store ptr ${rowU}, ptr ${rowSlot}`); B.br(lRow); }; diff --git a/packages/compiler/src/backend/llvm/lib-lookup.ts b/packages/compiler/src/backend/llvm/lib-lookup.ts index 9fc23cb94..a6e76e4e0 100644 --- a/packages/compiler/src/backend/llvm/lib-lookup.ts +++ b/packages/compiler/src/backend/llvm/lib-lookup.ts @@ -65,7 +65,7 @@ export function emitLookupConnect(host: LlvmEmitterContext, e: LibCallExpr): LlV " br i1 %more, label %body, label %finish", "body:", " %record = call ptr @scr_arr_get_ref(ptr %addresses, double %index)", - ` %addressp = getelementptr inbounds %${struct}, ptr %record, i64 0, i32 ${field + 1}`, + ` %addressp = getelementptr inbounds %${struct}, ptr %record, i64 0, i32 ${field + host.nullableUnions.fieldBase(addresses.elem.shapeId)}`, " %address = load ptr, ptr %addressp", " %owned.address = call ptr @scr_str_retain_v(ptr %address)", " call double @scr_arr_push_ref(ptr %ips, ptr %owned.address)", diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index 3ac9fe050..322479a85 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -168,7 +168,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) const i = shape.fields.findIndex((f) => f.name === name); if (i < 0) throw new InternalCompilerError(`llvm emitter bug: net.serverAddress record lacks ${name}`); - return i + 1; + return i + host.nullableUnions.fieldBase(recT.shapeId); }; const store = (name: string, ty: string, v: string): void => { const p = B.tmp(); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 02bf37192..67cb77047 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -144,6 +144,7 @@ export const LIB_FN_SYMS: Record = { "num.toFixed0": "scr_num_to_fixed0", "num.toFixed": "scr_num_to_fixed", "num.toStringRadix": "scr_num_to_string_radix", + "num.toStringRadixF64": "scr_num_to_string_radix_f64", "num.sameValue": "scr_num_same_value", "intl.numFormatEnUs": "scr_intl_num_format_en_us", "number.isFinite": "scr_num_is_finite", @@ -194,6 +195,7 @@ export const LIB_FN_SYMS: Record = { "os.cpusModel": "scr_os_cpus_model", "os.cpusField": "scr_os_cpus_field", "os.constants": "scr_os_constants", + "constants.object": "scr_node_constants", "os.release": "scr_os_release", "os.userName": "scr_os_user_name", "os.userShell": "scr_os_user_shell", @@ -405,6 +407,8 @@ export const LIB_FN_SYMS: Record = { "buffer.fromDyn": "scr_buffer_from_dyn", "buffer.concat": "scr_buffer_concat", "buffer.concatLen": "scr_buffer_concat_len", + "buffer.concatDyn": "scr_buffer_concat_dyn", + "buffer.concatLenDyn": "scr_buffer_concat_len_dyn", "buffer.alloc": "scr_buffer_alloc", "buffer.allocUnsafe": "scr_buffer_alloc_unsafe", "buffer.allocUnsafeSlow": "scr_buffer_alloc_unsafe_slow", @@ -638,6 +642,13 @@ export const LIB_FN_SYMS: Record = { "url.legacyFormat": "scr_url_legacy_format", "url.legacyResolve": "scr_url_legacy_resolve", "url.domainToASCII": "scr_url_domain_to_ascii", + "punycode.decode": "scr_punycode_decode", + "punycode.encode": "scr_punycode_encode", + "punycode.toASCII": "scr_punycode_to_ascii", + "punycode.toUnicode": "scr_punycode_to_unicode", + "punycode.ucs2Decode": "scr_punycode_ucs2_decode", + "punycode.ucs2Encode": "scr_str_from_code_point", + "punycode.warn": "scr_punycode_warn", "url.domainToUnicode": "scr_url_domain_to_unicode", "url.pathToFileURL": "scr_url_from_path", "url.pathToFileURLPlatform": "scr_url_from_path_platform", diff --git a/packages/compiler/src/backend/llvm/nullable-unions.test.ts b/packages/compiler/src/backend/llvm/nullable-unions.test.ts new file mode 100644 index 000000000..46bd688e7 --- /dev/null +++ b/packages/compiler/src/backend/llvm/nullable-unions.test.ts @@ -0,0 +1,83 @@ +import { expect, test } from "vitest"; +import { + F64, + NULL_T, + UNDEFINED_T, + type IrModule, + type IrType, + type IrUnionDef, +} from "../../ir/ir.js"; +import { NullableUnions } from "./nullable-unions.js"; + +const loc = { file: "nullable-unions.ts", start: 0, end: 0 }; +const a: IrType = { kind: "record", shapeId: "a" }; +const b: IrType = { kind: "record", shapeId: "b" }; +const c: IrType = { kind: "record", shapeId: "c" }; + +function module(unions: IrUnionDef[], extra: Partial = {}): IrModule { + return { + irVersion: 16, + sourceFile: loc.file, + entry: "main", + records: ["a", "b", "c"].map((id) => ({ id, fields: [{ name: id, type: F64 }] })), + unions, + functions: [], + ...extra, + }; +} + +test("unions of two or more records become shape unions", () => { + const unions = new NullableUnions( + module([ + { id: "ab", arms: [a, b] }, + { id: "abu", arms: [a, UNDEFINED_T, b] }, + { id: "abnu", arms: [a, b, NULL_T, UNDEFINED_T] }, + { id: "af", arms: [a, F64] }, + { id: "au", arms: [a, UNDEFINED_T] }, + ]), + ); + expect(unions.shapes("ab")).toMatchObject({ unitTag: -1, nullTag: -1 }); + expect(unions.shapes("abu")).toMatchObject({ unitTag: 1, nullTag: -1 }); + expect(unions.shapes("abnu")).toMatchObject({ unitTag: 3, nullTag: 2 }); + expect(unions.shapes("abnu")?.arms).toEqual([ + { tag: 0, shapeId: "a" }, + { tag: 1, shapeId: "b" }, + ]); + // A scalar arm keeps the box; one record arm is a single-arm nullable. + expect(unions.get("af")).toBeNull(); + expect(unions.single("au")).not.toBeNull(); + expect(unions.shapes("au")).toBeNull(); + // Tagged shapes number from 2 and move their fields after the word. + expect([unions.shapeNumber("a"), unions.shapeNumber("b"), unions.shapeNumber("c")]).toEqual([ + 2, 3, 0, + ]); + expect([unions.fieldBase("a"), unions.fieldBase("c")]).toEqual([2, 1]); + expect(unions.boxedUnions(new Map([["af", { id: "af", arms: [a, F64] }]])).size).toBe(1); +}); + +test("records of runtime-inspected unions keep every union over them boxed", () => { + const set: IrType = { kind: "set", elem: { kind: "union", unionId: "ab" } }; + const unions = new NullableUnions( + module( + [ + { id: "ab", arms: [a, b] }, + { id: "abu", arms: [a, b, UNDEFINED_T] }, + { id: "bc", arms: [b, c] }, + { id: "ac", arms: [a, c] }, + ], + { globals: [{ id: "%g.s", name: "s", type: set, mutable: false }] }, + ), + ); + expect(unions.get("ab")).toBeNull(); + expect(unions.get("abu")).toBeNull(); + expect(unions.get("bc")).toBeNull(); + expect(unions.get("ac")).toBeNull(); + expect(unions.shapeNumber("c")).toBe(0); +}); + +test("library, lifetime-region and debug builds keep record unions boxed", () => { + const unions = [{ id: "ab", arms: [a, b] }]; + expect(new NullableUnions(module(unions), false).get("ab")).toBeNull(); + expect(new NullableUnions(module(unions, { lifetimeRegions: true })).get("ab")).toBeNull(); + expect(new NullableUnions(module(unions)).shapes("ab")).not.toBeNull(); +}); diff --git a/packages/compiler/src/backend/llvm/nullable-unions.ts b/packages/compiler/src/backend/llvm/nullable-unions.ts index b1032f9d4..8729e9649 100644 --- a/packages/compiler/src/backend/llvm/nullable-unions.ts +++ b/packages/compiler/src/backend/llvm/nullable-unions.ts @@ -17,6 +17,7 @@ import { everyModuleNode } from "../../ir/traverse.js"; * every container, field, capture, parameter, and return slot holds the * plain pointer. Every other union keeps the tagged `%ScrUnion` box. */ export interface NullableUnion { + repr: "single"; unionId: string; refTag: number; /** The unit arm NULL encodes. */ @@ -24,7 +25,57 @@ export interface NullableUnion { /** The `null` arm of a two-unit-arm union, encoded by NULLABLE_NULL; * -1 when the union has one unit arm. */ nullTag: number; - arm: NullableRefArm; + /** One of NullableRefArm. Typed as the plain IrType so the self-hosted + * frontend keeps IrType's structurally identical arms one record shape + * (a PointerUnion arm holding the narrowed intersection would split them). */ + arm: IrType; +} + +/** A union of two or more record arms and at most two unit arms (`A | B`, + * `A | B | undefined`, discriminated unions of object literals) whose + * VALUES are the record pointer itself, like a NullableUnion: NULL is the + * unit arm (`undefined` when both exist, NULLABLE_NULL is then `null`). + * Every record shape that is an arm of such a union is a TAGGED shape: its + * struct carries the shape's module-wide number in a hidden `i32` word + * right after the reference count (struct index 1), stored by every + * allocation, so the arm of a pointer is that word mapped through the + * union's arm table (the per-union `@sc_stag.` helper). Records share + * one retain; release and trace dispatch on the arm. */ +export interface ShapeUnion { + repr: "shapes"; + unionId: string; + /** The unit arm NULL encodes; -1 when the union has no unit arm. */ + unitTag: number; + /** The `null` arm of a two-unit-arm union, encoded by NULLABLE_NULL; + * -1 otherwise. */ + nullTag: number; + /** The record arms in tag order. */ + arms: readonly { tag: number; shapeId: string }[]; +} + +/** A union whose values are plain pointers (no `%ScrUnion` box). */ +export type PointerUnion = NullableUnion | ShapeUnion; + +/** The struct index of a tagged record shape's hidden shape-number word. */ +export const SHAPE_WORD_INDEX = 1; + +/** The LLVM type through which any tagged record's shape word is read + * (the reference count, then the word). */ +export function shapeWordHeader(sizeType: "i32" | "i64"): string { + return `{ ${sizeType}, i32 }`; +} + +/** The per-union helper mapping a shape-union value to its tag. */ +export function shapeTagFn(unionId: string): string { + return `@sc_stag.${unionId.replace(/[^A-Za-z0-9_.]/g, "_")}`; +} + +/** The per-union release and trace dispatchers of a shape union. */ +export function shapeReleaseFn(unionId: string): string { + return `@sc_srelease.${unionId.replace(/[^A-Za-z0-9_.]/g, "_")}`; +} +export function shapeTraceFn(unionId: string): string { + return `@sc_strace.${unionId.replace(/[^A-Za-z0-9_.]/g, "_")}`; } /** The module-scope immortal sentinel standing for `null` in a @@ -48,44 +99,98 @@ export type NullableRefArm = | (IrType & { kind: "array" | "map" | "set" }); /** The arm is a JS object: always truthy, compared by identity. */ -export function isObjectArm(nullable: NullableUnion): boolean { - return nullable.arm.kind !== "string"; +export function isObjectArm(nullable: PointerUnion): boolean { + return nullable.repr === "shapes" || nullable.arm.kind !== "string"; } /** The representation decision for every union of one module. It is a * pure function of the module, so serialized-IR and in-memory emission * agree. */ export class NullableUnions { - private readonly byId = new Map(); + private readonly byId = new Map(); + /** Tagged record shapes (arms of a shape union) by their module-wide + * shape number (2 and up: 0 and 1 are the unit encodings' pseudo + * numbers, never stored). */ + private readonly shapeNumbers = new Map(); /** NULL-skipping retain wrappers requested so far: symbol → inner retain * call target. The emitter defines each once. */ readonly retainWrappers = new Map(); /** Some nullable union encodes `null` with NULLABLE_NULL. */ readonly usesNullSentinel: boolean = false; - constructor(mod: IrModule) { + /** `shapes`: allow shape unions (debug builds keep boxes, whose + * debug-info layouts describe every arm). */ + constructor(mod: IrModule, shapes = true) { const emitted = new Set(); for (const cls of mod.classes ?? []) if (!cls.runtime) emitted.add(cls.name); const records = new Set(); for (const shape of mod.records ?? []) records.add(shape.id); const boxed = boxedUnionIds(mod); + // Shape unions stay out of library builds (their host ABI marshals + // boxes) and of programs with lifetime regions (whose walks report a + // reference slot by its type's release entry point). + const allowShapes = shapes && mod.lib === undefined && mod.lifetimeRegions !== true; + // A record that is an arm of a boxed union never becomes a shape + // union's arm: conversions between unions over the same records then + // keep one representation. + const boxedRecords = new Set(); + for (const def of mod.unions ?? []) + if (boxed.has(def.id)) + for (const arm of def.arms) if (arm.kind === "record") boxedRecords.add(arm.shapeId); for (const def of mod.unions ?? []) { if (boxed.has(def.id)) continue; - const nullable = nullableShape(def, emitted, records); + const nullable = + nullableShape(def, emitted, records) ?? + (allowShapes ? shapeUnion(def, records, boxedRecords) : null); if (!nullable) continue; this.byId.set(def.id, nullable); if (nullable.nullTag >= 0) this.usesNullSentinel = true; + if (nullable.repr === "shapes") + for (const arm of nullable.arms) + if (!this.shapeNumbers.has(arm.shapeId)) + this.shapeNumbers.set(arm.shapeId, this.shapeNumbers.size + 2); } } - get(unionId: string): NullableUnion | null { + get(unionId: string): PointerUnion | null { return this.byId.get(unionId) ?? null; } - of(type: IrType): NullableUnion | null { + of(type: IrType): PointerUnion | null { return type.kind === "union" ? this.get(type.unionId) : null; } + /** The single-reference-arm form only (shape unions answer null). */ + single(unionId: string): NullableUnion | null { + const nullable = this.byId.get(unionId); + return nullable?.repr === "single" ? nullable : null; + } + + /** The shape-union form only. */ + shapes(unionId: string): ShapeUnion | null { + const nullable = this.byId.get(unionId); + return nullable?.repr === "shapes" ? nullable : null; + } + + /** Every shape union, in module order. */ + allShapeUnions(): ShapeUnion[] { + const out: ShapeUnion[] = []; + for (const nullable of this.byId.values()) if (nullable.repr === "shapes") out.push(nullable); + return out; + } + + /** The module-wide number a tagged record shape stores in its shape + * word, or 0 for shapes that carry none. */ + shapeNumber(shapeId: string): number { + return this.shapeNumbers.get(shapeId) ?? 0; + } + + /** The struct index of a record shape's first declared field: 2 for + * tagged shapes (after the shape word), 1 otherwise. */ + fieldBase(shapeId: string): number { + return this.shapeNumbers.has(shapeId) ? 2 : 1; + } + has(unionId: string): boolean { return this.byId.has(unionId); } @@ -94,10 +199,11 @@ export class NullableUnions { return this.byId.size; } - /** The reference arm of every nullable union, by union id. */ + /** The reference arm of every single-arm nullable union, by union id. */ armTypes(): Map { const result = new Map(); - for (const [id, nullable] of this.byId) result.set(id, nullable.arm); + for (const [id, nullable] of this.byId) + if (nullable.repr === "single") result.set(id, nullable.arm); return result; } @@ -129,7 +235,7 @@ function nullableShape( if (unitTag < 0 || nullTag < 0) return null; } const arm = def.arms[refTag]!; - const base = { unionId: def.id, refTag, unitTag, nullTag }; + const base = { repr: "single" as const, unionId: def.id, refTag, unitTag, nullTag }; if (arm.kind === "object" && emittedClasses.has(arm.className)) return { ...base, arm: { kind: "object", className: arm.className } }; if (arm.kind === "record" && records.has(arm.shapeId)) @@ -140,6 +246,34 @@ function nullableShape( return null; } +/** Two or more emitted record arms and at most two unit arms (both: + * undefined and null). */ +function shapeUnion( + def: IrUnionDef, + records: ReadonlySet, + boxedRecords: ReadonlySet, +): ShapeUnion | null { + const units = def.arms.filter(isUnitType).length; + if (units > 2 || def.arms.length - units < 2) return null; + const arms: { tag: number; shapeId: string }[] = []; + for (let tag = 0; tag < def.arms.length; tag++) { + const arm = def.arms[tag]!; + if (isUnitType(arm)) continue; + if (arm.kind !== "record" || !records.has(arm.shapeId) || boxedRecords.has(arm.shapeId)) + return null; + arms.push({ tag, shapeId: arm.shapeId }); + } + let unitTag = -1, + nullTag = -1; + if (units === 1) unitTag = def.arms.findIndex(isUnitType); + else if (units === 2) { + unitTag = def.arms.findIndex((arm) => arm.kind === "undefinedT"); + nullTag = def.arms.findIndex((arm) => arm.kind === "nullT"); + if (unitTag < 0 || nullTag < 0) return null; + } + return { repr: "shapes", unionId: def.id, unitTag, nullTag, arms }; +} + /** Unions the runtime itself inspects as tagged boxes keep that * representation: Map keys and Set elements hash and compare union keys by * tag and payload inside the C map. */ diff --git a/packages/compiler/src/backend/llvm/publish.ts b/packages/compiler/src/backend/llvm/publish.ts index c884339af..4f807424b 100644 --- a/packages/compiler/src/backend/llvm/publish.ts +++ b/packages/compiler/src/backend/llvm/publish.ts @@ -25,6 +25,7 @@ import type { IrClassDef, IrRecordShape, IrType, IrUnionDef, SrcLoc } from "../. import { mapOf, STRING, typeKey } from "../../ir/ir.js"; import { mangleClassStruct, mangleRecordStruct } from "../mangle.js"; import { FN_ATTRS } from "./shapes.js"; +import { shapeTagFn } from "./nullable-unions.js"; import type { InlineUnionFields } from "./inline-unions.js"; import { LlvmUnsupportedError } from "./unsupported.js"; @@ -44,6 +45,10 @@ export interface PublishHost { readonly inlineUnions: InlineUnionFields; /** The nullable-pointer representation of a union, when it has one. */ nullableArm(t: IrType): IrType | null; + /** The union's values are tagged record pointers (nullable-unions.ts). */ + shapeUnion(t: IrType): boolean; + /** The struct index of a record shape's first field. */ + fieldBase(shapeId: string): number; /** Class layout: hierarchy membership, preorder interval, field slots * (with their storage types), runtime roots. */ classInfo(className: string): PublishClassInfo; @@ -210,6 +215,19 @@ export class PublishWalkers { if (def.arms.some((arm) => scalarRefusal(arm))) throw new LlvmUnsupportedError(`publish of ${typeKey(t)}`, loc); const live = arms.flatMap((a) => (a.fn === null ? [] : [{ tag: a.tag, fn: a.fn }])); + if (this.host.shapeUnion(t)) { + // The value is the record itself (or a unit encoding): no box to + // mark; the arm's walker marks and walks the record. + const lines = [head, `entry:`]; + lines.push( + ` %tag = call i32 ${shapeTagFn(t.unionId)}(ptr %o)`, + ` switch i32 %tag, label %done [ ${live.map((a) => `i32 ${a.tag}, label %arm${a.tag}`).join(" ")} ]`, + ); + for (const a of live) + lines.push(`arm${a.tag}:`, ` call void ${a.fn}(ptr %o, ptr %ctx)`, ` br label %done`); + lines.push(`done:`, ` ret void`, `}`); + return lines; + } const lines = [ head, `entry:`, @@ -240,17 +258,18 @@ export class PublishWalkers { const shape = this.host.recordsById.get(t.shapeId); if (!shape) throw new LlvmUnsupportedError(`publish of an unknown record ${t.shapeId}`, loc); + const base = this.host.fieldBase(shape.id); const members = [ ...shape.fields.flatMap((f, i) => this.host.inlineUnions.record(f.type) ? [] - : [{ name: f.name, index: i + 1, type: f.type }], + : [{ name: f.name, index: i + base, type: f.type }], ), ...(shape.indexValue ? [ { name: "[key: string]", - index: shape.fields.length + 1, + index: shape.fields.length + base, type: mapOf(STRING, shape.indexValue), }, ] diff --git a/packages/compiler/src/backend/llvm/regex-matchers.test.ts b/packages/compiler/src/backend/llvm/regex-matchers.test.ts index a3627cb7f..1acd9c465 100644 --- a/packages/compiler/src/backend/llvm/regex-matchers.test.ts +++ b/packages/compiler/src/backend/llvm/regex-matchers.test.ts @@ -2,6 +2,7 @@ import { describe, expect, test } from "vitest"; import { compileRegexMatcher, regexLiteralBailReason, + regexLiteralCaptures, regexLiteralCompiles, } from "./regex-matchers.js"; @@ -11,6 +12,30 @@ function body(pattern: string, flags = "", vector = true): string { return code.lines.join("\n"); } +describe("regex literal captures", () => { + test("counts captures with the whole match and marks the ones every match sets", () => { + expect(regexLiteralCaptures("[&<>]", "g")).toEqual({ count: 1, always: [true] }); + expect(regexLiteralCaptures("(\\w+)=(\\d)+", "")).toEqual({ + count: 3, + always: [true, true, true], + }); + expect(regexLiteralCaptures("(?a)(b)?|(c)", "")).toEqual({ + count: 4, + always: [true, false, false, false], + }); + expect(regexLiteralCaptures("x(?:(a)|(b))y(c)*(d){0,2}(e){1,3}", "")).toEqual({ + count: 6, + always: [true, false, false, false, false, true], + }); + }); + + test("answers null outside the parsed subset", () => { + expect(regexLiteralCaptures("(?=a)(b)", "")).toBeNull(); + expect(regexLiteralCaptures("(a)\\1", "")).toBeNull(); + expect(regexLiteralCaptures("a", "v")).toBeNull(); + }); +}); + describe("regex literal matcher subset", () => { test.each([ ['^(\\d+\\.\\d+\\.\\d+\\.\\d+) - - \\[([^\\]]+)\\] "(\\w+) ([^ ?"]+)(?:\\?([^ "]*))?$', ""], diff --git a/packages/compiler/src/backend/llvm/regex-matchers.ts b/packages/compiler/src/backend/llvm/regex-matchers.ts index ad5c47c86..001f081e5 100644 --- a/packages/compiler/src/backend/llvm/regex-matchers.ts +++ b/packages/compiler/src/backend/llvm/regex-matchers.ts @@ -1639,6 +1639,40 @@ function parseSubset(pattern: string, flags: string): { root: RxNode; ncap: numb } } +/** The capture count of a regex literal (the whole match included) and, + * per capture, whether every match sets it: no alternation of several + * branches and no repetition that can run zero times encloses it. Null + * when the pattern is outside the parsed subset. */ +export function regexLiteralCaptures( + pattern: string, + flags: string, +): { count: number; always: boolean[] } | null { + const parsed = parseSubset(pattern, flags); + if (!parsed) return null; + const always = Array.from({ length: parsed.ncap }, (_, i) => i === 0); + const walk = (node: RxNode, sure: boolean): void => { + switch (node.kind) { + case "cap": + always[node.index] = sure; + walk(node.body, sure); + return; + case "seq": + for (const item of node.items) walk(item, sure); + return; + case "alt": + for (const alt of node.alts) walk(alt, sure && node.alts.length === 1); + return; + case "rep": + walk(node.body, sure && node.min > 0); + return; + default: + return; + } + }; + walk(parsed.root, true); + return { count: parsed.ncap, always }; +} + /** Whether a literal compiles to a native matcher (tests and size caps). */ export function regexLiteralCompiles(pattern: string, flags: string): boolean { return parseSubset(pattern, flags) !== null; diff --git a/packages/compiler/src/backend/llvm/regions.ts b/packages/compiler/src/backend/llvm/regions.ts index 41f67c0cb..b789c644d 100644 --- a/packages/compiler/src/backend/llvm/regions.ts +++ b/packages/compiler/src/backend/llvm/regions.ts @@ -101,8 +101,8 @@ export class LifetimeRegions { /** Can a value of type `t` hold (or own something that holds) a * reference to a ProgramLifetime instance the walk can explain? */ private reaches(t: IrType): boolean { - const arm = this.host.nullableUnions.of(t)?.arm; - if (arm !== undefined) return this.reaches(arm); + const nullable = this.host.nullableUnions.of(t); + if (nullable?.repr === "single") return this.reaches(nullable.arm); switch (t.kind) { case "object": return this.isLifetime(t.className) || this.reach.get(`object:${t.className}`) === true; @@ -287,7 +287,8 @@ export class LifetimeRegions { * ProgramLifetime instance). */ private reportLines(struct: string, f: Field, i: number): string[] { if (!isRefCounted(f.type) || !this.reaches(f.type)) return []; - const arm = this.host.nullableUnions.of(f.type)?.arm ?? f.type; + const nullable = this.host.nullableUnions.of(f.type); + const arm = nullable?.repr === "single" ? nullable.arm : f.type; const load = [ ` %f${i} = getelementptr inbounds %${struct}, ptr %o, i64 0, i32 ${f.index}`, ` %v${i} = load ptr, ptr %f${i}`, @@ -411,7 +412,7 @@ export class LifetimeRegions { ...this.reportBody( `define internal void ${walk}(ptr %o, ptr %w) ${FN_ATTRS} { ; region walk record ${shape.id}`, mangleRecordStruct(shape.id), - rcMembers(shape, this.host.inlineUnions), + rcMembers(shape, this.host.inlineUnions, this.host.nullableUnions.fieldBase(shape.id)), ), ); rows.push(`{ ptr, ptr } { ptr @${mangleRecordRelease(shape.id)}, ptr ${walk} }`); diff --git a/packages/compiler/src/backend/llvm/shapes.ts b/packages/compiler/src/backend/llvm/shapes.ts index 23565eff4..e390a4552 100644 --- a/packages/compiler/src/backend/llvm/shapes.ts +++ b/packages/compiler/src/backend/llvm/shapes.ts @@ -31,13 +31,23 @@ import { import { cycTypeDescriptor, emitObjectAlloc, emitObjectFree } from "./alloc.js"; import type { BlockBuilder } from "./blocks.js"; import { SCR_ARR_FIELD, llvmCommentText } from "./common.js"; -import type { NullableUnions } from "./nullable-unions.js"; +import { + NULLABLE_NULL, + SHAPE_WORD_INDEX, + shapeReleaseFn, + shapeTagFn, + shapeTraceFn, + shapeWordHeader, + type NullableUnions, + type ShapeUnion, +} from "./nullable-unions.js"; import { INLINE_UNION_FIELD, loadInlineUnion, stackUnionBox, type InlineUnionFields, } from "./inline-unions.js"; +import { recordMayKeepSource } from "./record-sources.js"; import { LlvmUnsupportedError } from "./unsupported.js"; /** What the tables need from the emitter: the extern-declaration ledger @@ -129,6 +139,7 @@ export { computeTraced } from "../cycle-analysis.js"; * `_v`-shaped. */ export function vAdapters(host: ShapeHost, t: IrType): { retain: string; release: string } { const nullable = host.nullableUnions.of(t); + if (nullable?.repr === "shapes") return shapeUnionRc(nullable); if (nullable) { const arm = vAdapters(host, nullable.arm); return { retain: nullableRetainSym(host, nullable.arm), release: arm.release }; @@ -184,6 +195,7 @@ export function vAdapters(host: ShapeHost, t: IrType): { retain: string; release * Families with a mirrored fast path call the inline helper instead. */ export function retainSym(host: ShapeHost, t: IrType): string { const nullable = host.nullableUnions.of(t); + if (nullable?.repr === "shapes") return shapeUnionRc(nullable).retain; if (nullable) return nullableRetainSym(host, nullable.arm); const stem = runtimeRcStem(t); if (host.rcHelpers !== null && stem !== null && inlineRcFamily(host, stem, "retain") !== null) { @@ -195,6 +207,99 @@ export function retainSym(host: ShapeHost, t: IrType): string { return vAdapters(host, t).retain; } +/** A shape union's RC entry points: every record retain is the same + * NULL- and immortal-skipping increment (the null sentinel is immortal), + * so the first arm's serves; release dispatches on the arm. */ +function shapeUnionRc(union: ShapeUnion): { retain: string; release: string } { + return { + retain: `@${mangleRecordRetain(union.arms[0]!.shapeId)}`, + release: shapeReleaseFn(union.unionId), + }; +} + +/** The tag, release and trace helpers of every shape union + * (nullable-unions.ts). The tag helper maps a value to its arm: NULL and + * the null sentinel are the unit arms, anything else a tagged record whose + * shape word names its arm (the switch folds to a lookup table). */ +export function emitShapeUnionHelpers(host: ShapeHost): string[] { + const out: string[] = []; + const S = host.sizeType; + for (const union of host.nullableUnions.allShapeUnions()) { + const header = shapeWordHeader(S); + const tagFn = shapeTagFn(union.unionId); + const cases = union.arms + .map((arm) => `i32 ${host.nullableUnions.shapeNumber(arm.shapeId)}, label %a${arm.tag}`) + .join(" "); + out.push( + `define internal i32 ${tagFn}(ptr %u) alwaysinline ${FN_ATTRS} { ; shape union tag`, + `entry:`, + ` %isnull = icmp eq ptr %u, null`, + ` br i1 %isnull, label %unit, label %ref`, + `unit:`, + ` ret i32 ${union.unitTag}`, + `ref:`, + ); + if (union.nullTag >= 0) + out.push( + ` %isnullarm = icmp eq ptr %u, ${NULLABLE_NULL}`, + ` br i1 %isnullarm, label %nullarm, label %rec`, + `nullarm:`, + ` ret i32 ${union.nullTag}`, + `rec:`, + ); + out.push( + ` %wp = getelementptr inbounds ${header}, ptr %u, i32 0, i32 ${SHAPE_WORD_INDEX}`, + ` %w = load i32, ptr %wp`, + ` switch i32 %w, label %bad [ ${cases} ]`, + ...union.arms.flatMap((arm) => [`a${arm.tag}:`, ` ret i32 ${arm.tag}`]), + `bad:`, + ` ret i32 -1`, + `}`, + ``, + ); + // release: unit encodings are NULL or immortal; each arm releases + // through its own (NULL- and immortal-tolerant) helper. + out.push( + `define internal void ${shapeReleaseFn(union.unionId)}(ptr %u) ${FN_ATTRS} { ; shape union release`, + `entry:`, + ` %t = call i32 ${tagFn}(ptr %u)`, + ` switch i32 %t, label %done [ ${union.arms.map((arm) => `i32 ${arm.tag}, label %a${arm.tag}`).join(" ")} ]`, + ...union.arms.flatMap((arm) => [ + `a${arm.tag}:`, + ` call void @${mangleRecordRelease(arm.shapeId)}(ptr %u)`, + ` br label %done`, + ]), + `done:`, + ` ret void`, + `}`, + ``, + ); + if (shapeUnionTraced(host, union)) + out.push( + `define internal void ${shapeTraceFn(union.unionId)}(ptr %u, ptr %visit, ptr %ctx) ${FN_ATTRS} { ; shape union trace`, + `entry:`, + ` %t = call i32 ${tagFn}(ptr %u)`, + ` switch i32 %t, label %done [ ${union.arms.map((arm) => `i32 ${arm.tag}, label %a${arm.tag}`).join(" ")} ]`, + ...union.arms.flatMap((arm) => [ + `a${arm.tag}:`, + ` call void @${mangleRecordTrace(arm.shapeId)}(ptr %u, ptr %visit, ptr %ctx)`, + ` br label %done`, + ]), + `done:`, + ` ret void`, + `}`, + ``, + ); + } + return out; +} + +/** A shape union's arms carry collector headers (all or none: the emitter + * traces every arm of a union with one traced arm). */ +function shapeUnionTraced(host: ShapeHost, union: ShapeUnion): boolean { + return union.arms.some((arm) => host.tracedShapes.has(`record:${arm.shapeId}`)); +} + /** The retain entry point of a nullable union's value: the arm's own when * it tolerates NULL (emitted class and record helpers), otherwise a * NULL-skipping wrapper around it (the null sentinel and literals are @@ -233,6 +338,7 @@ export function emitNullableRetainWrappers(host: ShapeHost): string[] { * Families with a mirrored fast path call the inline helper instead. */ export function releaseSym(host: ShapeHost, t: IrType): string { const nullable = host.nullableUnions.of(t); + if (nullable?.repr === "shapes") return shapeUnionRc(nullable).release; if (nullable) return releaseSym(host, nullable.arm); const stem = runtimeRcStem(t); if (host.rcHelpers !== null && stem !== null && inlineRcFamily(host, stem, "release") !== null) { @@ -643,6 +749,8 @@ export function traceAdapter(host: ShapeHost, t: IrType): string | null { return "@scr_promise_trace_v"; case "union": { const nullable = host.nullableUnions.get(t.unionId); + if (nullable?.repr === "shapes") + return shapeUnionTraced(host, nullable) ? shapeTraceFn(nullable.unionId) : null; if (nullable) return traceAdapter(host, nullable.arm); if (!host.tracedUnions.has(t.unionId)) return null; host.declare(`declare void @scr_union_trace_v(ptr, ptr, ptr)`); @@ -945,15 +1053,17 @@ export function mapValKindNum(value: IrType): number { export function rcMembers( shape: IrRecordShape, inline: InlineUnionFields, + /** The struct index of the first field (2 on tagged shapes). */ + base = 1, ): { index: number; type: IrType; name: string }[] { return [ ...shape.fields.flatMap((f, i) => - inline.record(f.type) ? [] : [{ index: i + 1, type: f.type, name: f.name }], + inline.record(f.type) ? [] : [{ index: i + base, type: f.type, name: f.name }], ), ...(shape.indexValue ? [ { - index: shape.fields.length + 1, + index: shape.fields.length + base, type: mapOf(STRING, shape.indexValue), name: "[key: string] overflow", }, @@ -1107,7 +1217,12 @@ export function directDestroyTarget( } function directDestroyKey(host: ShapeHost, type: IrType): string | null { - const t = host.nullableUnions.of(type)?.arm ?? type; + const nullable = host.nullableUnions.of(type); + let t = type; + if (nullable !== null) { + if (nullable.repr === "shapes") return null; + t = nullable.arm; + } if (t.kind === "record") return `record:${t.shapeId}`; return t.kind === "object" ? `object:${t.className}` : null; } @@ -1338,8 +1453,12 @@ export function emitRecordShapes( for (const shape of records) { const struct = mangleRecordStruct(shape.id); - const fieldTys = shape.fields.map((f) => llFieldType(f.type, host.inlineUnions.record(f.type))); + const fieldTys: string[] = shape.fields.map((f) => + llFieldType(f.type, host.inlineUnions.record(f.type)), + ); if (shape.indexValue) fieldTys.push("ptr"); // the overflow ScrMap * + // A tagged shape (an arm of a shape union) carries its shape number. + if (host.nullableUnions.shapeNumber(shape.id) !== 0) fieldTys.unshift("i32"); typeDefs.push( `%${struct} = type { ${host.sizeType}${fieldTys.length ? ", " + fieldTys.join(", ") : ""} } ` + `; record ${shape.id} { ${shape.fields.map((f) => llvmCommentText(f.name)).join("; ")}${shape.indexValue ? "; [key: string]" : ""} }`, @@ -1352,7 +1471,7 @@ export function emitRecordShapes( const candidates: [string, DirectDestroy][] = []; const memberTypes: IrType[] = []; for (const shape of records) { - const members = rcMembers(shape, host.inlineUnions); + const members = rcMembers(shape, host.inlineUnions, host.nullableUnions.fieldBase(shape.id)); for (const m of members) memberTypes.push(m.type); if (!members.some((m) => isRefCounted(m.type) && needsBoundedRelease(m.type))) continue; const release = mangleRecordRelease(shape.id); @@ -1370,7 +1489,9 @@ export function emitRecordShapes( for (const shape of records) { const struct = mangleRecordStruct(shape.id); const traced = host.tracedShapes.has(`record:${shape.id}`); - const members = rcMembers(shape, host.inlineUnions); + const base = host.nullableUnions.fieldBase(shape.id); + const shapeNumber = host.nullableUnions.shapeNumber(shape.id); + const members = rcMembers(shape, host.inlineUnions, base); const refMembers = members.filter((m) => isRefCounted(m.type)); const sizeOf = `ptrtoint (ptr getelementptr (%${struct}, ptr null, i32 1) to ${host.sizeType})`; @@ -1404,7 +1525,9 @@ export function emitRecordShapes( } t++; } - freeBody.push(...emitObjectFree(host, traced)); + // Closed layouts can carry a kept record source (record-sources.ts). + const observers = recordMayKeepSource(shape) ? "weak-or-source" : "weak"; + freeBody.push(...emitObjectFree(host, traced, observers)); defs.push( ...releaseBody( host, @@ -1439,6 +1562,11 @@ export function emitRecordShapes( ); nw.push(...emitObjectAlloc(host, sizeOf, traced ? { type: cycType } : null)); nw.push(` store ${host.sizeType} 1, ptr %o`); + if (shapeNumber !== 0) + nw.push( + ` %shp = getelementptr inbounds %${struct}, ptr %o, i64 0, i32 ${SHAPE_WORD_INDEX}`, + ` store i32 ${shapeNumber}, ptr %shp ; shape number`, + ); if (shape.indexValue) { // The overflow map (string-keyed): value handling is type-directed // exactly like shapes.ts's overflowNewC. @@ -1449,7 +1577,7 @@ export function emitRecordShapes( const trace = valKind === 2 ? traceArg(host, v) : "null"; nw.push( ` %ovf = call ptr @scr_map_new(i32 1, i32 ${valKind}, ptr ${host.mapOps(["null", "null", "null", rc.retain, rc.release, trace, "null"])})`, - ` %ovfp = getelementptr inbounds %${struct}, ptr %o, i64 0, i32 ${shape.fields.length + 1}`, + ` %ovfp = getelementptr inbounds %${struct}, ptr %o, i64 0, i32 ${shape.fields.length + base}`, ` store ptr %ovf, ptr %ovfp`, ); } @@ -1465,7 +1593,7 @@ export function emitRecordShapes( ]; let i = 0; for (const field of shape.fields) { - const index = i + 1; + const index = i + base; const inline = host.inlineUnions.record(field.type); const fieldTy = llFieldType(field.type, inline); clone.push( @@ -1514,7 +1642,7 @@ export function emitRecordShapes( ` call void ${releaseSym(host, m.type)}(ptr %v${i}) ; ${llvmCommentText(m.name)} (acyclic)`, ); }); - gf.push(...emitObjectFree(host, true), ` ret void`, `}`, ``); + gf.push(...emitObjectFree(host, true, observers), ` ret void`, `}`, ``); defs.push(...gf); } } diff --git a/packages/compiler/src/backend/llvm/stack-objects.ts b/packages/compiler/src/backend/llvm/stack-objects.ts index e9f437f14..0d65d9456 100644 --- a/packages/compiler/src/backend/llvm/stack-objects.ts +++ b/packages/compiler/src/backend/llvm/stack-objects.ts @@ -21,6 +21,7 @@ import type { CallLifetimes } from "./call-lifetimes.js"; import { emitCallExpr, emitConstruction } from "./expr-calls.js"; import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import { emitRecordLitFields } from "./expr-primitives.js"; +import { SHAPE_WORD_INDEX } from "./nullable-unions.js"; import { llFieldType } from "./shapes.js"; export type StackObjectSource = IrExpr & { kind: "recordLit" | "recordClone" | "new" | "call" }; @@ -196,12 +197,13 @@ export function stackObjectFields( const cached = host.stackObjectFieldPtrs.get(slot); if (cached) return cached; const struct = stackObjectStruct(type); + const base = host.nullableUnions.fieldBase(type.shapeId); const fields: StackObjectField[] = []; host.recordShape(type.shapeId).fields.forEach((field, index) => { if (!isRefCounted(field.type) || host.inlineUnions.record(field.type)) return; const ptr = host.B.slot(); host.B.entryAllocas.push( - `${ptr} = getelementptr inbounds ${struct}, ptr ${slot}, i64 0, i32 ${index + 1}`, + `${ptr} = getelementptr inbounds ${struct}, ptr ${slot}, i64 0, i32 ${index + base}`, ); fields.push({ name: field.name, ptr, type: field.type }); }); @@ -296,6 +298,17 @@ export function emitStackObject( } } B.line(`store ${host.sizeType} -1, ptr ${slot}`); + if (type.kind === "record") { + // A tagged shape's word names it to every shape union's tag read. + const shapeNumber = host.nullableUnions.shapeNumber(type.shapeId); + if (shapeNumber !== 0) { + const p = B.tmp(); + B.line( + `${p} = getelementptr inbounds ${struct}, ptr ${slot}, i64 0, i32 ${SHAPE_WORD_INDEX}`, + ); + B.line(`store i32 ${shapeNumber}, ptr ${p}`); + } + } if (e.kind === "call") { // The entry writes the result into the slot it receives last. emitCallExpr( diff --git a/packages/compiler/src/backend/llvm/string-slices.test.ts b/packages/compiler/src/backend/llvm/string-slices.test.ts index 53cca6417..b2656e560 100644 --- a/packages/compiler/src/backend/llvm/string-slices.test.ts +++ b/packages/compiler/src/backend/llvm/string-slices.test.ts @@ -1,7 +1,11 @@ import { expect, test } from "vitest"; -import { F64, STRING, VOID, type IrExpr, type IrFunction, type IrStmt } from "../../ir/ir.js"; +import { BOOL, F64, STRING, VOID, type IrExpr, type IrFunction, type IrStmt } from "../../ir/ir.js"; import { analyzeCallLifetimes } from "./call-lifetimes.js"; -import { findScalarStringSlices } from "./string-slices.js"; +import { + findLentStringParameters, + findScalarStringSlices, + findScratchStringSlices, +} from "./string-slices.js"; const loc = { file: "slices.ts", start: 0, end: 0 }; const ref = (id: string): IrExpr => ({ kind: "varRef", localId: id, type: STRING, loc }); function fixture(): IrFunction { @@ -95,3 +99,146 @@ test.each(["escape", "capture", "write", "other-consumer", "suspend"])( expect(facts(fn).size).toBe(0); }, ); + +const slice = (receiver: string, start: number, end: number): IrExpr => ({ + kind: "strIntrinsic", + method: "slice", + receiver: ref(receiver), + args: [ + { kind: "numLit", value: start, type: F64, loc }, + { kind: "numLit", value: end, type: F64, loc }, + ], + type: STRING, + loc, +}); +const equals = (left: IrExpr): IrExpr => ({ + kind: "strEq", + negated: false, + left, + right: { kind: "strLit", value: "GET", type: STRING, loc }, + type: BOOL, + loc, +}); +const parse = (value: IrExpr): IrExpr => ({ + kind: "libCall", + fn: "num.fromString", + args: [value], + type: F64, + loc, +}); +function lending(body: IrStmt[]): IrFunction { + return { + name: "scan", + params: [{ localId: "source", name: "source", type: STRING }], + locals: [ + { id: "source", name: "source", type: STRING, mutable: false }, + { id: "word", name: "word", type: STRING, mutable: false }, + ], + returnType: VOID, + loc, + body, + }; +} +function scratch(fn: IrFunction, extra: IrFunction[] = []): Set { + const functions = new Map([fn, ...extra].map((f) => [f.name, f])); + const lifetimes = analyzeCallLifetimes(functions); + return findScratchStringSlices( + fn, + lifetimes, + findScalarStringSlices(fn, lifetimes), + findLentStringParameters(functions, lifetimes), + ); +} +test("slices read only by lending consumers use a stack buffer", () => { + const inline = slice("source", 0, 3); + const parsed = slice("source", 4, 6); + const init = slice("source", 1, 5); + const fn = lending([ + { kind: "exprStmt", expr: equals(inline), loc }, + { kind: "exprStmt", expr: parse(parsed), loc }, + { kind: "varDecl", localId: "word", init, loc }, + { kind: "exprStmt", expr: equals(ref("word")), loc }, + { kind: "exprStmt", expr: parse(ref("word")), loc }, + ]); + expect(scratch(fn)).toEqual(new Set([inline, parsed, init])); +}); +test.each(["return", "concat", "slice-of-slice", "capture", "async"])( + "a slice local that may be kept (%s) stays owned", + (reason) => { + const init = slice("source", 1, 5); + const fn = lending([ + { kind: "varDecl", localId: "word", init, loc }, + { kind: "exprStmt", expr: equals(ref("word")), loc }, + ]); + if (reason === "return") fn.body.push({ kind: "return", value: ref("word"), loc }); + if (reason === "concat") + fn.body.push({ + kind: "exprStmt", + expr: { + kind: "strConcat", + left: { kind: "strLit", value: "", type: STRING, loc }, + right: ref("word"), + type: STRING, + loc, + }, + loc, + }); + if (reason === "slice-of-slice") + fn.body.push({ kind: "exprStmt", expr: equals(slice("word", 0, 2)), loc }); + if (reason === "capture") fn.locals[1]!.boxed = true; + if (reason === "async") fn.async = true; + expect(scratch(fn).has(init)).toBe(false); + }, +); +function helper(name: string, body: IrStmt[]): IrFunction { + return { + name, + params: [{ localId: "text", name: "text", type: STRING }], + locals: [{ id: "text", name: "text", type: STRING, mutable: false }], + returnType: BOOL, + loc, + body, + }; +} +const callOf = (callee: string, arg: IrExpr): IrExpr => ({ + kind: "call", + callee, + args: [arg], + type: BOOL, + loc, +}); +test("helper parameters read only by lending consumers accept stack slices", () => { + const recursive = helper("isGet", [ + { + kind: "return", + value: { + kind: "logical", + op: "||", + left: equals(ref("text")), + right: callOf("isGet", ref("text")), + type: BOOL, + loc, + }, + loc, + }, + ]); + const keeps = helper("keeps", [{ kind: "return", value: callOf("stores", ref("text")), loc }]); + const stores = helper("stores", [ + { + kind: "exprStmt", + expr: { kind: "arrayLit", elems: [ref("text")], type: { kind: "array", elem: STRING }, loc }, + loc, + }, + { kind: "return", value: { kind: "boolLit", value: true, type: BOOL, loc }, loc }, + ]); + const lent = slice("source", 0, 3); + const kept = slice("source", 0, 4); + const fn = lending([ + { kind: "exprStmt", expr: callOf("isGet", lent), loc }, + { kind: "exprStmt", expr: callOf("keeps", kept), loc }, + ]); + const functions = [recursive, keeps, stores]; + expect(scratch(fn, functions)).toEqual(new Set([lent])); + const all = new Map([fn, ...functions].map((f) => [f.name, f])); + expect([...findLentStringParameters(all, analyzeCallLifetimes(all)).keys()]).toEqual(["isGet"]); +}); diff --git a/packages/compiler/src/backend/llvm/string-slices.ts b/packages/compiler/src/backend/llvm/string-slices.ts index 71a26e2f3..ae73f1ea1 100644 --- a/packages/compiler/src/backend/llvm/string-slices.ts +++ b/packages/compiler/src/backend/llvm/string-slices.ts @@ -1,4 +1,9 @@ -import { type IrExpr, type IrFunction, type IrStmt } from "../../ir/ir.js"; +import { + type IrExpr, + type IrFunction, + type IrStmt, + type IrStrIntrinsicMethod, +} from "../../ir/ir.js"; import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js"; import { F64_INF, f64Lit, sizeToDouble } from "./common.js"; import type { CallLifetimes } from "./call-lifetimes.js"; @@ -6,6 +11,12 @@ import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; type Slice = IrExpr & { kind: "strIntrinsic" }; +function isSlice(value: IrExpr): value is Slice { + return ( + value.kind === "strIntrinsic" && (value.method === "slice" || value.method === "substring") + ); +} + /** A local used exclusively for scalar indexing needs no copied bytes. * Snapshot the receiver and normalized bounds at its initializer, including * side effects in the bounds. Escapes, captures, writes, suspension, and all @@ -111,3 +122,234 @@ export function emitStringSliceRead(host: LlvmEmitterContext, value: Slice): LlV ); return { name: result, type: value.type }; } + +/** Bytes of the stack buffer behind one scratch slice, header included. */ +export const SCRATCH_SLICE_BYTES = 96; + +/** String methods that read a lent receiver or argument only during their + * runtime call and never return it. Methods returning a string can return + * their unchanged receiver, so they stay on the owned path. */ +const LENDING_METHODS: ReadonlySet = new Set([ + "length", + "charCodeAt", + "indexOf", + "includes", + "startsWith", + "endsWith", +]); + +/** Numeric parsers read their text during the call. */ +const LENDING_LIB_FNS: ReadonlySet = new Set([ + "num.fromString", + "num.parseInt", + "num.parseFloat", +]); + +/** Typed string-keyed collection operations: reads probe with the key, and + * an insertion stores scr_str_scratch_keep's copy (scr_map_append). */ +const LENDING_KEY_METHODS: ReadonlySet = new Set(["get", "has", "set", "add", "delete"]); + +function stringKeyed(value: IrExpr & { kind: "mapIntrinsic" | "setIntrinsic" }): boolean { + const receiver = value.receiver.type; + if (receiver.kind === "map") + return receiver.key.kind === "string" && receiver.value.kind !== "dyn"; + if (receiver.kind === "set") return receiver.elem.kind === "string"; + return false; +} + +/** String parameters, by function and index, that the body reads only in + * lending positions (findLentStringParameters). */ +export type LentStringParameters = ReadonlyMap>; + +/** The operands `value` reads only while it runs, keeping no reference. */ +function lentOperands(value: IrExpr, parameters: LentStringParameters): readonly IrExpr[] { + switch (value.kind) { + case "strEq": + case "strCmp": + return [value.left, value.right]; + case "strIntrinsic": + return LENDING_METHODS.has(value.method) ? [value.receiver, ...value.args] : []; + case "libCall": + return LENDING_LIB_FNS.has(value.fn) ? value.args.slice(0, 1) : []; + case "mapIntrinsic": + case "setIntrinsic": + return LENDING_KEY_METHODS.has(value.method) && stringKeyed(value) + ? value.args.slice(0, 1) + : []; + case "call": { + const lent = parameters.get(value.callee); + return lent ? value.args.filter((_, index) => lent.has(index)) : []; + } + default: + return []; + } +} + +interface LendingScan { + /** Slice operands of lending consumers, excluding any node also reached + * in an owning position. */ + scratch: Set; + /** Candidates read anywhere other than a lending position. */ + invalid: Set; + /** Direct callees, for the parameter fixpoint. */ + callees: Set; +} + +/** One pass over `fn`: which candidate bindings only lending consumers read + * (a slice initializer of a candidate local is not itself a read), and + * which slice operands those consumers lend. A switch discriminant is only + * compared with the case tests. */ +function scanLending( + fn: IrFunction, + candidates: ReadonlySet, + initializers: ReadonlyMap, + parameters: LentStringParameters, +): LendingScan { + const scan: LendingScan = { scratch: new Set(), invalid: new Set(), callees: new Set() }; + const owned = new Set(); + function lent(value: IrExpr): boolean { + if (value.kind === "varRef" && candidates.has(value.localId)) return true; + if (!isSlice(value)) return expr(value); + scan.scratch.add(value); + return everyExprChild(value, expr, stmt); + } + function expr(value: IrExpr): boolean { + if (isSlice(value)) owned.add(value); + if (value.kind === "varRef" && candidates.has(value.localId)) scan.invalid.add(value.localId); + if (value.kind === "closure" || value.kind === "classRef") + for (const id of value.captures ?? []) scan.invalid.add(id); + if (value.kind === "call") scan.callees.add(value.callee); + const operands = lentOperands(value, parameters); + if (operands.length === 0) return everyExprChild(value, expr, stmt); + return everyExprChild( + value, + (child) => (operands.includes(child) ? lent(child) : expr(child)), + stmt, + ); + } + function stmt(value: IrStmt): boolean { + // A candidate's single declaration initializes it with its slice. + if (value.kind === "varDecl" && value.init !== null && initializers.has(value.localId)) + return everyExprChild(value.init, expr, stmt); + if (value.kind === "switch" && value.disc.type.kind === "string") { + lent(value.disc); + for (const entry of value.cases) { + if (entry.test) expr(entry.test); + entry.body.forEach(stmt); + } + return true; + } + return everyStmtChild(value, expr, stmt); + } + fn.body.forEach(stmt); + for (const node of owned) scan.scratch.delete(node); + return scan; +} + +/** String parameters that every body reads only in lending positions, + * including lent arguments of other such parameters: a caller may pass a + * scratch slice to them. Start from every borrowed (unchanged) string + * parameter of a synchronous function and drop violations until nothing changes, so + * recursion through a lent parameter stays lent. A coroutine can keep its + * parameters past the caller's frame, so it never lends. */ +export function findLentStringParameters( + functions: ReadonlyMap, + lifetimes: CallLifetimes, +): Map> { + const lent = new Map>(); + for (const fn of functions.values()) { + if (fn.async || fn.generator) continue; + // Borrowed parameters are the unchanged ones nothing redeclares. + const stable = lifetimes.borrowed.get(fn.name); + const indexes = new Set(); + fn.params.forEach((param, index) => { + if (param.type.kind === "string" && stable?.has(index)) indexes.add(index); + }); + if (indexes.size > 0) lent.set(fn.name, indexes); + } + const callers = new Map>(); + const pending = new Set(lent.keys()); + while (pending.size > 0) { + const name = pending.values().next().value as string; + pending.delete(name); + const indexes = lent.get(name); + const fn = functions.get(name); + if (!indexes || !fn) continue; + const ids = new Set([...indexes].map((index) => fn.params[index]!.localId)); + const scan = scanLending(fn, ids, new Map(), lent); + for (const callee of scan.callees) { + let set = callers.get(callee); + if (!set) callers.set(callee, (set = new Set())); + set.add(name); + } + let changed = false; + for (const index of [...indexes]) { + if (!scan.invalid.has(fn.params[index]!.localId)) continue; + indexes.delete(index); + changed = true; + } + if (!changed) continue; + if (indexes.size === 0) lent.delete(name); + const affected = callers.get(name); + if (affected) for (const caller of affected) if (lent.has(caller)) pending.add(caller); + } + return lent; +} + +/** Slices whose every reader borrows them are written into a stack buffer + * instead of a heap allocation (scr_str_slice_scratch): a slice operand of + * a lending consumer, and the initializer of a stable local read only by + * lending consumers. The copy is ASCII, bounded, and immortal for retain and + * release, so it neither pins its source nor needs to be freed; longer or + * non-ASCII results keep the ordinary owned string. Each site owns its + * buffer: a site runs again only after its previous value is dead, since + * nothing can keep it. Coroutine frames do not keep stack buffers across a + * suspension, so they are left alone. */ +export function findScratchStringSlices( + fn: IrFunction, + lifetimes: CallLifetimes, + scalar: ReadonlyMap, + parameters: LentStringParameters = new Map(), +): Set { + if (fn.async || fn.generator) return new Set(); + const bindings = lifetimes.bindings.get(fn.name); + const locals = new Map(); + everyStmtList(fn.body, { + expr: () => true, + stmt: (stmt) => { + if ( + stmt.kind === "varDecl" && + bindings?.has(stmt.localId) && + !scalar.has(stmt.localId) && + stmt.init !== null && + isSlice(stmt.init) + ) + locals.set(stmt.localId, stmt.init); + return true; + }, + }); + const scan = scanLending(fn, new Set(locals.keys()), locals, parameters); + for (const [id, init] of locals) if (!scan.invalid.has(id)) scan.scratch.add(init); + return scan.scratch; +} + +/** Write a scratch slice into its own stack buffer. The result is owned: + * releasing the scratch copy does nothing, and a heap fallback is freed. */ +export function emitScratchStringSlice( + host: LlvmEmitterContext, + slice: Slice, + receiver: LlValue, + args: readonly LlValue[], +): LlValue { + const B = host.B; + const buffer = B.slot(); + B.entryAllocas.push(`${buffer} = alloca [${SCRATCH_SLICE_BYTES} x i8], align 8`); + host.declare( + `declare ptr @scr_str_slice_scratch(ptr, double, double, i1 zeroext, ptr, ${host.sizeType})`, + ); + const result = B.tmp(); + B.line( + `${result} = call ptr @scr_str_slice_scratch(ptr ${receiver.name}, double ${args[0]?.name ?? f64Lit(0)}, double ${args[1]?.name ?? F64_INF}, i1 ${slice.method === "substring" ? "true" : "false"}, ptr ${buffer}, ${host.sizeType} ${SCRATCH_SLICE_BYTES})`, + ); + return host.own({ name: result, type: slice.type }); +} diff --git a/packages/compiler/src/backend/llvm/union-repr.ts b/packages/compiler/src/backend/llvm/union-repr.ts index 415aeb851..959f9e51e 100644 --- a/packages/compiler/src/backend/llvm/union-repr.ts +++ b/packages/compiler/src/backend/llvm/union-repr.ts @@ -2,14 +2,19 @@ * helper generators (walkers, dyn). A tagged union is a `%ScrUnion` box; a * nullable union (nullable-unions.ts) is the arm pointer itself: NULL for * its NULL-encoded unit arm and, in `C | null | undefined`, the immortal - * NULLABLE_NULL sentinel for `null`. Every structural read of a union value + * NULLABLE_NULL sentinel for `null`. A shape union's value is likewise the + * record pointer; its tag comes from the record's shape word. Every structural read of a union value * goes through here. */ import type { BlockBuilder } from "./blocks.js"; -import { NULLABLE_NULL, type NullableUnion } from "./nullable-unions.js"; +import { NULLABLE_NULL, shapeTagFn, type PointerUnion } from "./nullable-unions.js"; /** The i32 tag of a union value. */ -export function emitUnionTag(B: BlockBuilder, nullable: NullableUnion | null, u: string): string { +export function emitUnionTag(B: BlockBuilder, nullable: PointerUnion | null, u: string): string { const t = B.tmp(); + if (nullable?.repr === "shapes") { + B.line(`${t} = call i32 ${shapeTagFn(nullable.unionId)}(ptr ${u})`); + return t; + } if (nullable) { const isNull = B.tmp(); B.line(`${isNull} = icmp eq ptr ${u}, null`); @@ -32,8 +37,13 @@ export function emitUnionTag(B: BlockBuilder, nullable: NullableUnion | null, u: /** i1: a nullable union value holds its reference arm (neither NULL nor * the `null` sentinel). */ -export function emitNullablePresent(B: BlockBuilder, nullable: NullableUnion, u: string): string { +export function emitNullablePresent(B: BlockBuilder, nullable: PointerUnion, u: string): string { const present = B.tmp(); + if (nullable.repr === "shapes" && nullable.unitTag < 0) { + // No unit arm: every value is a record (NULL only as a pending dummy). + B.line(`${present} = icmp ne ptr ${u}, null`); + return present; + } B.line(`${present} = icmp ne ptr ${u}, null`); if (nullable.nullTag < 0) return present; const notNullArm = B.tmp(), @@ -46,12 +56,18 @@ export function emitNullablePresent(B: BlockBuilder, nullable: NullableUnion, u: /** i1: a nullable union value holds the arm `tag` (`negated`: does not). */ export function emitNullableIsTag( B: BlockBuilder, - nullable: NullableUnion, + nullable: PointerUnion, u: string, tag: number, negated: boolean, ): string { - if (tag === nullable.refTag) { + if (nullable.repr === "shapes" && tag !== nullable.unitTag && tag !== nullable.nullTag) { + const actual = emitUnionTag(B, nullable, u), + t = B.tmp(); + B.line(`${t} = icmp ${negated ? "ne" : "eq"} i32 ${actual}, ${tag}`); + return t; + } + if (nullable.repr === "single" && tag === nullable.refTag) { const present = emitNullablePresent(B, nullable, u); if (!negated) return present; const t = B.tmp(); @@ -65,7 +81,7 @@ export function emitNullableIsTag( } /** The BORROWED payload pointer of a reference arm. */ -export function emitUnionPeek(B: BlockBuilder, nullable: NullableUnion | null, u: string): string { +export function emitUnionPeek(B: BlockBuilder, nullable: PointerUnion | null, u: string): string { if (nullable) return u; const p = B.tmp(); const t = B.tmp(); @@ -76,7 +92,9 @@ export function emitUnionPeek(B: BlockBuilder, nullable: NullableUnion | null, u /** Text-template forms for helpers written as raw LLVM lines: `%out` * receives the tag / payload of `u`. */ -export function unionTagLines(nullable: NullableUnion | null, u: string, out: string): string[] { +export function unionTagLines(nullable: PointerUnion | null, u: string, out: string): string[] { + if (nullable?.repr === "shapes") + return [` ${out} = call i32 ${shapeTagFn(nullable.unionId)}(ptr ${u})`]; if (nullable && nullable.nullTag < 0) return [ ` ${out}.isnull = icmp eq ptr ${u}, null`, @@ -95,7 +113,7 @@ export function unionTagLines(nullable: NullableUnion | null, u: string, out: st ]; } -export function unionPeekLines(nullable: NullableUnion | null, u: string, out: string): string[] { +export function unionPeekLines(nullable: PointerUnion | null, u: string, out: string): string[] { // A plain copy of the pointer (NULL included) under the template's name. if (nullable) return [` ${out} = getelementptr i8, ptr ${u}, i64 0`]; return [ diff --git a/packages/compiler/src/backend/llvm/union-retype.test.ts b/packages/compiler/src/backend/llvm/union-retype.test.ts index eca6adcd6..0f0abdb68 100644 --- a/packages/compiler/src/backend/llvm/union-retype.test.ts +++ b/packages/compiler/src/backend/llvm/union-retype.test.ts @@ -183,7 +183,9 @@ test("a nullable-pointer source rebuilds the target box", () => { expect(retag).not.toContain("retype.share"); }); -async function llvmFor(source: string): Promise<{ module: IrModule; ir: string }> { +/** `boxed`: keep every union a tagged box (record unions otherwise become + * shape unions, which retype without any box). */ +async function llvmFor(source: string, boxed = true): Promise<{ module: IrModule; ir: string }> { const dir = await mkdtemp(join(tmpdir(), "scriptc-union-retype-")); try { const entry = join(dir, "main.ts"), @@ -194,7 +196,7 @@ async function llvmFor(source: string): Promise<{ module: IrModule; ir: string } throw new Error(result.diagnostics.map((item) => `${item.code}: ${item.message}`).join("\n")); const module = deserializeModule(await readFile(output, "utf8")); expect(validateModule(module)).toEqual([]); - return { module, ir: emitLlvmModule(module) }; + return { module, ir: emitLlvmModule(boxed ? keepUnionsBoxed(module) : module) }; } finally { await rm(dir, { recursive: true, force: true }); } @@ -253,3 +255,16 @@ console.log(leafValue(parsed)); expect(stored).not.toBeNull(); expect(ir).toContain(`load i8, ptr ${stored![1]}`); }); + +test("record unions retype as the record pointer itself", async () => { + const { module, ir } = await llvmFor(VISITOR, false); + const helpers = module.functions.filter((fn) => fn.name.startsWith("%union.retag.")); + expect(helpers.length).toBeGreaterThan(0); + for (const helper of helpers) { + const symbol = `sc_bf__x25_union_retag_${helper.name.slice("%union.retag.".length)}`; + const retag = body(ir, symbol); + expect(retag).not.toMatch(/@scr_union_new|retype\.share|sc_retype_guard/); + } + // No record of the visitor is ever boxed. + expect(ir).not.toMatch(/call ptr @scr_union_new_ref/); +}); diff --git a/packages/compiler/src/backend/llvm/walkers.ts b/packages/compiler/src/backend/llvm/walkers.ts index 9b8d80b87..46dbc86e1 100644 --- a/packages/compiler/src/backend/llvm/walkers.ts +++ b/packages/compiler/src/backend/llvm/walkers.ts @@ -28,6 +28,7 @@ import { traceAdapter, type ShapeHost, } from "./shapes.js"; +import { disposeHookLoadLines, RECORD_SOURCES_GATE } from "./alloc.js"; import { recordMayKeepSource, recordSourceFieldOrder } from "./record-sources.js"; import { LlvmUnsupportedError } from "./unsupported.js"; import { emitUnionPeek, emitUnionTag } from "./union-repr.js"; @@ -284,7 +285,8 @@ export class LlWalkers { throw new InternalCompilerError( `llvm emitter bug: jsonStringify of unknown shape ${shapeId}`, ); - const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + 1])); + const fieldBase = this.host.nullableUnions.fieldBase(shapeId); + const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + fieldBase])); if (recordMayKeepSource(shape)) this.emitRecordSourceWriter(B, shapeId); // CYCLE-CAPABLE shapes bracket the walk with the circular-detection // stack; edge labels stamp before members whose walk can re-enter — @@ -396,6 +398,24 @@ export class LlWalkers { const writer = this.recordFieldWriter(shapeId); const lSource = B.newLabel("jw.source"); const lNative = B.newLabel("jw.native"); + // Only a program that can keep record sources looks one up (the gate + // folds to a constant), and only once one was kept: keeping a source + // installs the dispose hook, so a NULL hook means the table is empty. + const lHooked = B.newLabel("jw.hooked"); + const kept = B.tmp(); + B.line(`${kept} = load i1, ptr ${RECORD_SOURCES_GATE}`); + B.condBr(kept, lHooked, lNative); + B.startBlock(lHooked); + const hook = B.tmp(); + const hookLines = disposeHookLoadLines(host, hook); + if (hookLines !== null) { + const lProbe = B.newLabel("jw.probe"); + for (const line of hookLines) B.line(line.trimStart()); + const installed = B.tmp(); + B.line(`${installed} = icmp ne ptr ${hook}, null`); + B.condBr(installed, lProbe, lNative); + B.startBlock(lProbe); + } if (traceAdapter(host, { kind: "record", shapeId }) === null) { // One lookup: the runtime writes the record when it kept a source. host.declare(`declare zeroext i1 @scr_jb_try_record_source(ptr, ptr, ptr)`); @@ -437,7 +457,8 @@ export class LlWalkers { throw new InternalCompilerError(`llvm emitter bug: field writer of unknown shape ${shapeId}`); const name = `sc_jwf_${this.recordFieldWriters.size}`; this.recordFieldWriters.set(shapeId, name); - const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + 1])); + const fieldBase = this.host.nullableUnions.fieldBase(shapeId); + const fieldIndex = new Map(shape.fields.map((f, i) => [f.name, i + fieldBase])); const byName = new Map(shape.fields.map((f) => [f.name, f])); const cyclic = traceAdapter(this.host, { kind: "record", shapeId }) !== null; const order = recordSourceFieldOrder(shape); @@ -534,7 +555,7 @@ export class LlWalkers { const ovfp = B.tmp(); const ovf = B.tmp(); B.line( - `${ovfp} = getelementptr inbounds %${mangleRecordStruct(shape.id)}, ptr %v, i64 0, i32 ${shape.fields.length + 1}`, + `${ovfp} = getelementptr inbounds %${mangleRecordStruct(shape.id)}, ptr %v, i64 0, i32 ${shape.fields.length + this.host.nullableUnions.fieldBase(shape.id)}`, ); B.line(`${ovf} = load ptr, ptr ${ovfp} ; overflow map`); if (iv.kind === "dyn") { diff --git a/packages/compiler/src/backend/mangle.ts b/packages/compiler/src/backend/mangle.ts index 0bc428964..43c9cea71 100644 --- a/packages/compiler/src/backend/mangle.ts +++ b/packages/compiler/src/backend/mangle.ts @@ -204,6 +204,11 @@ export function mangleGenSpawn(fnName: string): string { export function mangleGenDrop(fnName: string): string { return `sc_gd_${sanitize(fnName)}`; } +/** The never-started return() hook of a generator that closes its source + * argument (an iterator helper). */ +export function mangleGenSourceClose(fnName: string): string { + return `sc_gc_${sanitize(fnName)}`; +} /** Emitted ref-kind resolve thunk, interned per inner-type key. */ export function mangleResolveThunk(n: number): string { return `sc_rslv_${n}`; diff --git a/packages/compiler/src/backend/may-throw.ts b/packages/compiler/src/backend/may-throw.ts index ac45129f0..f07bbfa78 100644 --- a/packages/compiler/src/backend/may-throw.ts +++ b/packages/compiler/src/backend/may-throw.ts @@ -580,7 +580,7 @@ function breaksTo( /** Statements no execution reaches: those after a statement that cannot * complete normally in the same statement list. */ -function unreachableStatements(body: readonly IrStmt[]): Set { +export function unreachableStatements(body: readonly IrStmt[]): Set { const dead = new Set(); const { statement } = completion(); const list = (stmts: readonly IrStmt[]): void => { diff --git a/packages/compiler/src/backend/stack-observations.test.ts b/packages/compiler/src/backend/stack-observations.test.ts new file mode 100644 index 000000000..f58c89ad8 --- /dev/null +++ b/packages/compiler/src/backend/stack-observations.test.ts @@ -0,0 +1,137 @@ +import { expect, test } from "vitest"; +import { + F64, + STRING, + VOID, + type IrExpr, + type IrFunction, + type IrModule, + type IrStmt, +} from "../ir/ir.js"; +import { captureFreeFunctions, observesStacks } from "./stack-observations.js"; + +const loc = { file: "stack-observations.ts", start: 0, end: 0 }; +const ERROR = { kind: "object", className: "%Error" } as const; +const err: IrExpr = { loc, kind: "varRef", localId: "e", type: ERROR }; +const stackRead: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "libCall", fn: "error.stack", args: [err], type: STRING }, +}; +const call = (callee: string): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee, args: [], type: VOID }, +}); +const throwStmt: IrStmt = { loc, kind: "throw", value: err }; +const ret: IrStmt = { loc, kind: "return", value: null }; +const fn = (name: string, body: IrStmt[]): IrFunction => ({ + loc, + name, + params: [], + locals: [], + returnType: VOID, + body, +}); +const mod = (functions: IrFunction[], extra: Partial = {}): IrModule => ({ + irVersion: 16, + sourceFile: "stack-observations.ts", + entry: "%main", + functions, + classes: [], + records: [], + unions: [], + globals: [], + ...extra, +}); + +test("a reachable stack read is observed", () => { + expect(observesStacks(mod([fn("%main", [call("parse")]), fn("parse", [stackRead])]))).toBe(true); +}); + +test("programs without stack reads are not", () => { + expect(observesStacks(mod([fn("%main", [call("helper")]), fn("helper", [ret])]))).toBe(false); +}); + +test("reads after an unconditional throw or return never run", () => { + expect(observesStacks(mod([fn("%main", [throwStmt, stackRead])]))).toBe(false); + expect(observesStacks(mod([fn("%main", [ret, call("parse")]), fn("parse", [stackRead])]))).toBe( + false, + ); + // A terminator ends only its own list: the outer list continues. + const cond: IrExpr = { loc, kind: "numLit", value: 1, type: F64 }; + expect( + observesStacks( + mod([fn("%main", [{ kind: "if", cond, then: [ret], else_: null, loc }, stackRead])]), + ), + ).toBe(true); +}); + +test("functions referenced only from dead code are unreached", () => { + // The production React shape: a DEV-only body behind a throw stub calls + // the stack parser, and a closure created after the stub reaches it too. + const closure: IrStmt = { + loc, + kind: "assign", + localId: "replacer", + value: { + loc, + kind: "closure", + fnName: "replacer", + captures: [], + type: { kind: "func", params: [], ret: VOID }, + }, + }; + const program = mod([ + fn("%main", [call("outline")]), + fn("outline", [throwStmt, closure, call("parse")]), + fn("replacer", [call("parse")]), + fn("parse", [stackRead]), + ]); + expect(observesStacks(program)).toBe(false); + // The same closure created before the stub keeps the read reachable. + const live = mod([ + fn("%main", [call("outline")]), + fn("outline", [closure, throwStmt]), + fn("replacer", [call("parse")]), + fn("parse", [stackRead]), + ]); + expect(observesStacks(live)).toBe(true); +}); + +test("class methods are roots", () => { + const program = mod([fn("%main", [ret]), fn("%Parser.read", [stackRead])], { + classes: [ + { name: "Parser", fields: [], methods: ["read"] } as unknown as NonNullable< + IrModule["classes"] + >[number], + ], + }); + expect(observesStacks(program)).toBe(true); +}); + +test("leaf functions that cannot create an Error need no frame", () => { + const num = (value: number): IrExpr => ({ loc, kind: "numLit", value, type: F64 }); + const add: IrStmt = { + loc, + kind: "return", + value: { loc, kind: "bin", op: "+", left: num(1), right: num(2), type: F64 }, + }; + const construct: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "libCall", fn: "error.new", args: [], type: ERROR }, + }; + const program = mod([ + fn("%main", [call("leaf"), call("caller"), call("creates"), call("viaCreates")]), + fn("leaf", [add]), + fn("caller", [call("leaf"), add]), + fn("creates", [construct]), + fn("viaCreates", [call("creates")]), + fn("recursive", [call("recursive")]), + ]); + const free = captureFreeFunctions(program, new Set(["recursive"]), null); + expect([...free].sort()).toEqual(["caller", "leaf"]); + // A worker program keeps every frame. + expect(captureFreeFunctions({ ...program, workers: true }, null, null).size).toBe(0); +}); diff --git a/packages/compiler/src/backend/stack-observations.ts b/packages/compiler/src/backend/stack-observations.ts new file mode 100644 index 000000000..7debf0751 --- /dev/null +++ b/packages/compiler/src/backend/stack-observations.ts @@ -0,0 +1,237 @@ +/* Whole-program stack-trace gate (see observesStacks). Pure function of the + * IR module; the emitter keeps a source frame in every function only when + * 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 { MAY_THROW_ARR_METHODS } from "../ir/ir.js"; +import { everyExprChild, everyStmtChild, everyStmtList } from "../ir/traverse.js"; +import { unreachableStatements } from "./may-throw.js"; +import type { PublishedTypes } from "./publication.js"; +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); +} + +function containsStackRead(fn: IrFunction): boolean { + return !everyStmtList(fn.body, { stmt: () => true, expr: (expr) => !readsStack(expr) }); +} + +/** Visit every expression of `body` that execution can reach: statements + * after one that cannot complete normally in the same list are skipped. */ +function visitLiveExprs(body: IrStmt[], visit: (expr: IrExpr) => void): void { + const dead = unreachableStatements(body); + const expr = (node: IrExpr): boolean => { + visit(node); + return everyExprChild(node, expr, stmt); + }; + const stmt = (node: IrStmt): boolean => dead.has(node) || everyStmtChild(node, expr, stmt); + body.every(stmt); +} + +/** Whether the program can read a captured stack trace, which is what makes + * every function keep a source frame. A read counts when it can execute: + * it sits in live code of a function reachable from the module's roots (its + * entry, library exports, class prototype helpers and every class's + * methods) through live code. Code after a statement that cannot complete + * normally is not live: production builds of libraries such as React's + * Flight server keep development-only stack parsing behind `throw` stubs, + * 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 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; + } + } + } + const reached = new Set(); + let observed = false; + while (pending.length > 0 && !observed) { + const name = pending.pop()!; + if (reached.has(name)) continue; + reached.add(name); + const fn = byName.get(name); + if (fn === undefined) continue; + visitLiveExprs(fn.body, (expr) => { + if (readsStack(expr)) 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); + }); + } + return observed; +} + +/** Expressions that neither create an Error nor throw one by themselves. + * Calls, `new` and virtual calls are capture-free when their callees are. */ +const CAPTURE_FREE_EXPRS: ReadonlySet = new Set([ + "numLit", + "boolLit", + "strLit", + "unitLit", + "varRef", + "incDec", + "assignExpr", + "bin", + "unary", + "logical", + "ternary", + "nullish", + "toBool", + "fieldGet", + "recordGet", + "arrayGet", + "arrayHas", + "arrayState", + "strCmp", + "strEq", + "strConcat", + "upcast", + "unionDisc", + "unionIsTag", + "unionEq", + "instanceOf", + "recordLit", + "arrayLit", + "unionWrap", + "unionNarrow", + "libCall", + "arrIntrinsic", + "call", + "new", + "virtualCall", +]); + +/** Statements that neither create an Error nor throw one by themselves. */ +const CAPTURE_FREE_STMTS: ReadonlySet = new Set([ + "varDecl", + "assign", + "exprStmt", + "if", + "while", + "doWhile", + "for", + "switch", + "block", + "return", + "break", + "continue", + "fieldSet", + "recordSet", + "arraySet", +]); + +/** Functions whose frame no stack capture can ever include: a capture runs + * where an Error is created (by the program or by a throwing runtime + * operation), and these functions only perform operations from a fixed + * list that never create one and only call functions of the same kind. + * Their frames are therefore never observable and the emitter omits them, + * which keeps small leaf methods and helpers free of the per-call frame + * push in programs that read stacks. Recursive functions (the stack guard + * throws RangeError), async and generator bodies, and every function of a + * worker program keep their frames. */ +export function captureFreeFunctions( + mod: IrModule, + stackChecks: ReadonlySet | null, + published: PublishedTypes | null, +): Set { + const free = new Set(); + if (mod.workers === true) return free; + const methodImpls = new Map(); + for (const cls of mod.classes ?? []) { + for (const m of cls.methods ?? []) { + let list = methodImpls.get(m); + if (!list) methodImpls.set(m, (list = [])); + list.push(`%${cls.name}.${m}`); + } + } + const tdzGlobals = new Set((mod.globals ?? []).filter((g) => g.tdz).map((g) => g.id)); + const callees = new Map(); + for (const fn of mod.functions) { + if (fn.async === true || fn.generator !== undefined || stackChecks?.has(fn.name)) continue; + const tdz = new Set([...tdzGlobals, ...fn.locals.filter((l) => l.tdz).map((l) => l.id)]); + const calls: string[] = []; + const bigint = (e: IrExpr): boolean => e.type.kind === "bigint"; + const expr = (e: IrExpr): boolean => { + if (!CAPTURE_FREE_EXPRS.has(e.kind)) return false; + switch (e.kind) { + case "varRef": + case "incDec": + case "assignExpr": + if (tdz.has(e.localId)) return false; + break; + case "bin": + // BigInt division and remainder by zero throw RangeError. + if (bigint(e) || bigint(e.left) || bigint(e.right)) return false; + break; + case "unary": + if (bigint(e) || bigint(e.operand)) return false; + break; + case "libCall": + // Math functions only: they neither throw nor call back. + if (!e.fn.startsWith("math.") || MAY_THROW_LIB_FNS.has(e.fn)) return false; + break; + case "arrIntrinsic": { + // Methods that cannot throw, over numbers, booleans or strings + // (no element conversion can run program code) and without a + // callback argument. + const elem = e.receiver.type.kind === "array" ? e.receiver.type.elem.kind : null; + if ( + MAY_THROW_ARR_METHODS.has(e.method) || + (elem !== "f64" && elem !== "bool" && elem !== "string") || + e.args.some((arg) => arg.type.kind === "func") + ) + return false; + break; + } + case "call": + calls.push(e.callee); + break; + case "new": + calls.push(`%${e.className}.constructor`); + break; + case "virtualCall": { + const impls = methodImpls.get(e.method); + if (impls === undefined) return false; + calls.push(...impls); + break; + } + } + return everyExprChild(e, expr, stmt); + }; + const stmt = (s: IrStmt): boolean => { + if (!CAPTURE_FREE_STMTS.has(s.kind)) return false; + if (s.kind === "assign" && tdz.has(s.localId)) return false; + if (published !== null && writesPublished(published, s)) return false; + return everyStmtChild(s, expr, stmt); + }; + if (fn.body.every(stmt)) callees.set(fn.name, calls); + } + // Drop candidates that call a function outside the set until none do. + for (const name of callees.keys()) free.add(name); + let changed = true; + while (changed) { + changed = false; + for (const [name, calls] of callees) { + if (free.has(name) && !calls.every((callee) => free.has(callee))) { + free.delete(name); + changed = true; + } + } + } + return free; +} diff --git a/packages/compiler/src/cli/command.ts b/packages/compiler/src/cli/command.ts index d729de866..79442f19e 100644 --- a/packages/compiler/src/cli/command.ts +++ b/packages/compiler/src/cli/command.ts @@ -7,8 +7,10 @@ import { coverageEnvelope, coveragePasses, renderCoverage, + renderExplainDynamic, type CoverageFailOn, } from "../coverage/report.js"; +import { TIER_MODES, type TierMode } from "../frontend/tier-analysis.js"; import { buildEnvelope } from "../diagnostics/envelope.js"; import { renderWarnings } from "../diagnostics/render.js"; import { isNativeOptimization } from "../backend/optimization.js"; @@ -45,8 +47,15 @@ function parseCli( ): ReturnType< typeof parseArgs<{ options: typeof CLI_OPTIONS; allowPositionals: true; allowNegative: true }> > { + // `--explain-dynamic` takes an optional value: alone it lists every unit. + const normalized = args.map((arg) => (arg === "--explain-dynamic" ? "--explain-dynamic=" : arg)); try { - return parseArgs({ args, options: CLI_OPTIONS, allowPositionals: true, allowNegative: true }); + return parseArgs({ + args: normalized, + options: CLI_OPTIONS, + allowPositionals: true, + allowNegative: true, + }); } catch (err) { // parseArgs appends a paragraph about `--` and positionals to the // unknown-option message; the first sentence is the part that names @@ -91,6 +100,8 @@ async function main(args: string[], host: CliHost): Promise { externalTypeArgs.length > 0 || values.out !== undefined || values["emit-ir"] || + values.tier !== undefined || + values["explain-dynamic"] !== undefined || !values["keep-llvm"] ) { fail( @@ -152,10 +163,12 @@ async function main(args: string[], host: CliHost): Promise { values["windows-subsystem"] !== undefined || values["detached-exit"] || (values["npm-static"] ?? []).length > 0 || - externalTypeArgs.length > 0 + externalTypeArgs.length > 0 || + values.tier !== undefined || + values["explain-dynamic"] !== undefined ) { fail( - "scriptc build --lib takes no --backend/--emit/--print/--optimization/--strip/--windows-subsystem/--detached-exit/--npm-static/--ffi/--external-types: the profile pins the emission and optimization, npm imports are judged automatically, outbound FFI belongs to executable builds, and external type mappings belong to coverage", + "scriptc build --lib takes no --backend/--emit/--print/--optimization/--strip/--windows-subsystem/--detached-exit/--npm-static/--ffi/--external-types/--tier/--explain-dynamic: the profile pins the emission and optimization, npm imports are judged automatically, outbound FFI belongs to executable builds, external type mappings belong to coverage, and library artifacts are static-tier only", ); } const profilePath = resolve(profileArg); @@ -223,6 +236,15 @@ async function main(args: string[], host: CliHost): Promise { fail(`--fail-on is a coverage option (a build already fails on blockers)\n\n${USAGE}`); } const failOn = failOnRaw as CoverageFailOn | undefined; + const tierRaw = values.tier; + if (tierRaw !== undefined && !(TIER_MODES as readonly string[]).includes(tierRaw)) { + fail(`unknown --tier value "${tierRaw}" (supported: ${TIER_MODES.join(", ")})\n\n${USAGE}`); + } + const tier = tierRaw as TierMode | undefined; + const explainDynamic = values["explain-dynamic"]; + if (explainDynamic !== undefined && command !== "coverage") { + fail(`--explain-dynamic is a coverage option\n\n${USAGE}`); + } if (printNativeLinkInfo && command !== "build") { fail(`--print=native-link-info is a build option\n\n${USAGE}`); } @@ -263,6 +285,11 @@ async function main(args: string[], host: CliHost): Promise { } externalTypes[specifier] = declarationPath; } + const wasmArgs = values.wasm ?? []; + if (wasmArgs.length > 0 && command !== "coverage") { + fail(`--wasm is a coverage-only option\n\n${USAGE}`); + } + const wasmFiles = wasmArgs.map((file) => resolve(file)); const ffiProfilePath = values.ffi !== undefined ? resolve(values.ffi) : undefined; if (values.backend !== undefined && values.backend !== "llvm") { fail(`unknown backend "${values.backend}" (supported: llvm)\n\n${USAGE}`); @@ -340,6 +367,9 @@ async function main(args: string[], host: CliHost): Promise { ...(npmStatic !== undefined ? { npmStatic } : {}), ...(ffiProfilePath !== undefined ? { ffiProfilePath } : {}), ...(Object.keys(externalTypes).length > 0 ? { externalTypes } : {}), + ...(tier !== undefined ? { tier } : {}), + ...(explainDynamic !== undefined ? { explainDynamic: true } : {}), + ...(wasmFiles.length > 0 ? { wasm: wasmFiles } : {}), }); if (printDiagnostics) { // Keep stdout pure JSON for tooling. @@ -354,6 +384,18 @@ async function main(args: string[], host: CliHost): Promise { process.stdout.write( renderCoverage(coverage, { color, sourceTexts, root: process.cwd() }) + "\n", ); + if (explainDynamic !== undefined && coverage.tiers !== undefined) { + process.stdout.write( + "\n" + + renderExplainDynamic(coverage.tiers, { + color, + sourceTexts, + root: process.cwd(), + query: explainDynamic, + }) + + "\n", + ); + } } return coveragePasses(coverage, failOn) ? 0 : 1; } @@ -386,6 +428,7 @@ async function main(args: string[], host: CliHost): Promise { ...(npmStatic !== undefined ? { npmStatic } : {}), ...(ffiProfilePath !== undefined ? { ffiProfilePath } : {}), ...(printNativeLinkInfo ? { nativeLinkInfo: true } : {}), + ...(tier !== undefined ? { tier } : {}), }); if (!result.ok) { if (printDiagnostics) { diff --git a/packages/compiler/src/cli/usage.ts b/packages/compiler/src/cli/usage.ts index a0388374b..26b439167 100644 --- a/packages/compiler/src/cli/usage.ts +++ b/packages/compiler/src/cli/usage.ts @@ -57,7 +57,8 @@ Options: compile the named npm packages' shipped JS statically as program modules (repeatable; "auto" opts in every eligible direct import: own .d.ts, unminified JS, no build-transform - markers). Packages that fail preflight are refused with a + markers; "pkg..." also names every package pkg depends + on). Packages that fail preflight are refused with a diagnostic. Eligible dependencies compile automatically. --provenance-sources EXPERIMENTAL: compile npm dependencies from their @@ -69,6 +70,21 @@ Options: local declaration file. The declaration supplies types for analysis; the host module remains an explicit external-boundary blocker (repeatable) + --tier + tier policy (default: auto). static-only keeps every + construct without a static lowering a compile error, so + the program has no dynamic-tier demotions. Both modes + currently compile identically + --explain-dynamic[=] + coverage only: list the functions that operate on dynamic + values and the bindings, fields, functions, classes and + modules the dynamic tier would take over, with the + refusal behind each; a name shows only that unit's chain + --wasm + coverage only: report a WebAssembly module the program + loads whose path its sources do not spell (repeatable; + modules the sources name, inline modules and packaged + .wasm files are found without it) -h, --help show this help -v, --version print the version `; @@ -90,6 +106,9 @@ export const CLI_OPTIONS = { "npm-static": { type: "string", multiple: true }, "provenance-sources": { type: "boolean", default: false }, "external-types": { type: "string", multiple: true }, + tier: { type: "string" }, + "explain-dynamic": { type: "string" }, + wasm: { type: "string", multiple: true }, lib: { type: "boolean", default: false }, profile: { type: "string" }, help: { type: "boolean", short: "h", default: false }, diff --git a/packages/compiler/src/compile-types.ts b/packages/compiler/src/compile-types.ts index de2c1a335..2773af71b 100644 --- a/packages/compiler/src/compile-types.ts +++ b/packages/compiler/src/compile-types.ts @@ -3,6 +3,7 @@ import type { NativeLinkInfo } from "./backend/native-link-info.js"; import type { NativeOptimization } from "./backend/optimization.js"; import type { ScrDiagnostic } from "./diagnostics/diagnostic.js"; import type { CoverageInput } from "./coverage/report.js"; +import type { TierMode } from "./frontend/tier-analysis.js"; export type CompileOutputKind = "ir" | "llvm" | "asm" | "obj" | "exe"; @@ -45,6 +46,10 @@ export interface CompileBaseOptions { /** Attach the machine-readable external link recipe to an object result. * Valid only with outputKind "obj"; it never invokes a linker. */ nativeLinkInfo?: boolean; + /** `--tier` (frontend/tier-analysis.ts). "static-only" keeps every demotion + * request a refusal (design invariant I2). Until demotion lands both modes + * compile exactly as before. */ + tier?: TierMode; } /** Executable compile options. This remains the compatibility type for the @@ -161,6 +166,14 @@ export interface AnalyzeOptions { * to analyze project code, but imported runtime values remain explicit * SC1010 blockers rather than being counted as executable. */ externalTypes?: Readonly>; + /** `--tier` (see CompileBaseOptions.tier). */ + tier?: TierMode; + /** `--explain-dynamic`: attach the tier-assignment report + * (CoverageInput.tiers). Such analyses bypass the verdict cache. */ + explainDynamic?: boolean; + /** `--wasm `: WebAssembly module files the program loads that its + * sources do not name (absolute paths); reported with the discovered ones. */ + wasm?: readonly string[]; } export interface AnalyzeResult { diff --git a/packages/compiler/src/coverage/report.ts b/packages/compiler/src/coverage/report.ts index 9d0776d9a..6570f23d1 100644 --- a/packages/compiler/src/coverage/report.ts +++ b/packages/compiler/src/coverage/report.ts @@ -17,10 +17,14 @@ import { type DiagnosticsEnvelope, type LocatedDiagnostic, type SourcePosition, + type TierUnitPosition, + type TiersEnvelope, } from "../diagnostics/envelope.js"; import { renderDiagnostics } from "../diagnostics/render.js"; import type { LowerStats } from "../frontend/lowering/lowerer.js"; import type { ProvenanceSources } from "../frontend/provenance-registry.js"; +import type { DemotionUnit, TierReport } from "../frontend/tier-analysis.js"; +import type { WasmModuleReport } from "../wasm/discover.js"; export interface CoverageInput { file: string; @@ -51,6 +55,12 @@ export interface CoverageInput { /** --provenance-sources: diagnostics of elided pure-annotated dead * consts (off the build; the provenance section names them). */ provenanceElided?: ScrDiagnostic[]; + /** --explain-dynamic: the report-only tier assignment (which bindings, + * fields, functions, classes and modules the dynamic tier would take). */ + tiers?: TierReport; + /** The WebAssembly modules the build can see (wasm/discover.ts): their + * size, validity and the proposals they use. */ + wasmModules?: WasmModuleReport[]; /** True when tsc preflight failed — no coverage can be computed. */ preflightFailed: boolean; } @@ -61,6 +71,8 @@ export interface NpmStaticStatus { status: "static" | "refused"; /** The first refusal reason for a refused package. */ detail?: string; + /** How to act on the refusal, when there is a specific remedy. */ + hint?: string; } const GREEN = "\x1b[32m"; @@ -180,6 +192,12 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions out.push(` statements analyzed ${total}`); out.push(` compile statically ${ok} ${c(pctColor, c(BOLD, `(${pct}%)`))}`); + const dynamic = dynamicStatements(input); + if (dynamic > 0) { + out.push( + ` in dynamic tier ${dynamic} ${c(DIM, "(operate on values typed only at runtime; --explain-dynamic lists them)")}`, + ); + } if (skipped > 0) { out.push( @@ -203,6 +221,51 @@ 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. + 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)")}`, + ); + for (const m of wasm) { + const name = m.path !== undefined ? displayPath(m.path, opts.root) : "inline module"; + const state = + m.status === "valid" + ? c(GREEN, "valid") + : m.status === "unsupported" + ? c(YELLOW, `uses ${m.unsupported.join(", ")}`) + : c(RED, `${m.status}: ${m.error ?? ""}`); + out.push(` ${name} ${c(DIM, `${groupDigits(m.size)} bytes`)} ${state}`); + const imports = m.importModules.length > 0 ? ` (${m.importModules.join(", ")})` : ""; + out.push( + ` ${c(DIM, `${counted(m.functions, "function")}, ${counted(m.imports, "import")}${imports}, ${counted(m.exports, "export")}`)}`, + ); + if (m.features.length > 0) out.push(` ${c(DIM, `features: ${m.features.join(", ")}`)}`); + const shown = m.sources.slice(0, SITES_SHOWN); + for (const s of shown) { + const at = + s.line !== undefined + ? `${displayPath(s.file, opts.root)}:${s.line}` + : displayPath(s.file, opts.root); + const how = + s.kind === "path" + ? `path at ${at}` + : s.kind === "inline" + ? `inline at ${at}` + : s.kind === "package" + ? `shipped by the package at ${displayPath(dirOf(s.file), opts.root)}` + : `configured by ${at}`; + out.push(` ${c(DIM, `found: ${how}`)}`); + } + if (m.sources.length > shown.length) { + out.push(` ${c(DIM, `found: +${m.sources.length - shown.length} more`)}`); + } + } + out.push(""); + } + // --provenance-sources: per-package attribution — the answer to "did // the DEPENDENCY's statements compile static?", computed by summing // statsByFile entries under each package's source dir. Elided @@ -321,6 +384,27 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions return out.join("\n"); } +/** "1 function", "1,698 functions". */ +function counted(n: number, noun: string): string { + return `${groupDigits(n)} ${noun}${n === 1 ? "" : "s"}`; +} + +/** 637405 → "637,405". */ +function groupDigits(n: number): string { + const digits = String(n); + let out = ""; + for (let i = 0; i < digits.length; i++) { + if (i > 0 && (digits.length - i) % 3 === 0) out += ","; + out += digits[i]; + } + return out; +} + +function dirOf(file: string): string { + const slash = file.lastIndexOf("/"); + return slash <= 0 ? file : file.slice(0, slash); +} + function DIMPath(file: string, color: boolean): string { return color ? DIM + file + RESET : file; } @@ -432,13 +516,148 @@ export function coverageEnvelope( stats: { statementsTotal: total, statementsStatic: ok, - statementsDynamic: 0, + statementsDynamic: dynamicStatements(input), statementsFailed: failed, functionsSkipped: skipped, percentStatic: total === 0 ? 100 : Math.floor((ok / total) * 100), }, }), + ...(input.tiers !== undefined ? { tiers: tiersEnvelope(input.tiers, locator) } : {}), + ...(input.wasmModules !== undefined ? { wasmModules: input.wasmModules } : {}), groups, diagnostics: located, }; } + +/** Reached statements in dynamic-tier functions, never more than the + * compiled statements they are part of. */ +function dynamicStatements(input: CoverageInput): number { + const compiled = input.stats.statementsTotal - input.stats.statementsFailed; + return Math.max(0, Math.min(input.stats.statementsDynamic ?? 0, compiled)); +} + +function tiersEnvelope(tiers: TierReport, locator: SourceLocator): TiersEnvelope { + const at = (loc: { file: string; start: number }): SourcePosition => ({ + file: loc.file, + ...locator.position(loc.file, loc.start), + }); + const unit = (u: DemotionUnit): TierUnitPosition => ({ + kind: u.kind, + name: u.name, + ...(u.owner !== undefined ? { owner: u.owner } : {}), + ...at(u.loc), + }); + return { + mode: tiers.mode, + statementsDynamic: tiers.statementsDynamic, + dynamicFunctions: tiers.dynamicFunctions.map((fn) => ({ + name: fn.name, + ...at(fn.loc), + operation: fn.operation, + operationAt: at(fn.operationLoc), + statements: fn.statements, + })), + requests: tiers.requests.map((r) => ({ + unit: unit(r.unit), + enclosing: unit(r.enclosing), + seed: r.seed, + scope: r.scope, + code: r.code, + message: r.message, + ...at(r.loc), + })), + units: tiers.units.map((u) => ({ ...unit(u), requests: u.requests, sites: u.sites })), + }; +} + +/** IR function names as a user reads them: `%init.3` is a module's top + * level, `%Point.norm` the method `Point.norm`; generated helpers keep + * their `%`-prefixed names. */ +function functionDisplayName(name: string): string { + if (name.startsWith("%init.")) return ""; + if (/^%[A-Za-z_$][\w$]*\.[A-Za-z_$#][\w$]*$/.test(name)) return name.slice(1); + return name; +} + +function unitLabel(u: DemotionUnit, root: string | undefined): string { + if (u.kind === "module") return `module ${displayPath(u.name, root)}`; + if (u.kind === "binding" && u.owner !== undefined) return `binding ${u.name} in ${u.owner}`; + return `${u.kind} ${u.name}`; +} + +function unitMatches(u: DemotionUnit, query: string): boolean { + return ( + u.name === query || + u.name.endsWith(`.${query}`) || + u.name.split(" ")[0] === query || + `${u.owner ?? ""}:${u.name}` === query + ); +} + +/** `scriptc coverage --explain-dynamic[=]`: the dynamic tier today + * and the demotion chains the report-only analysis computed. With a query, + * only units whose name matches it (`x`, `Point.x`, `parse:x`). */ +export function renderExplainDynamic( + tiers: TierReport, + opts: CoverageRenderOptions & { query?: string } = {}, +): string { + const c = (code: string, s: string) => (opts.color ? code + s + RESET : s); + const locator = new SourceLocator(opts.sourceTexts); + const where = (loc: { file: string; start: number }) => { + const p = locator.position(loc.file, loc.start); + return `${displayPath(loc.file, opts.root)}:${p.line}:${p.column}`; + }; + const query = opts.query !== undefined && opts.query !== "" ? opts.query : undefined; + const out: string[] = []; + out.push( + ` ${c(BOLD, "dynamic tier")} ${c(DIM, `(tier assignment, report-only; --tier=${tiers.mode})`)}`, + ); + if (query === undefined) { + const fns = tiers.dynamicFunctions; + out.push( + ` ${fns.length} function${fns.length === 1 ? "" : "s"} already operate${fns.length === 1 ? "s" : ""} on dynamic values ${c(DIM, `(${tiers.statementsDynamic} statement${tiers.statementsDynamic === 1 ? "" : "s"})`)}`, + ); + for (const fn of fns) { + out.push( + ` ${functionDisplayName(fn.name)} ${c(DIM, `${where(fn.loc)}: ${fn.operation} at ${where(fn.operationLoc)}`)}`, + ); + } + } + const live = tiers.requests.filter((r) => r.scope !== "unreached" && r.seed !== "excluded"); + const excluded = tiers.requests.filter((r) => r.seed === "excluded").length; + const unreached = tiers.requests.filter((r) => r.scope === "unreached").length; + if (query === undefined) { + out.push(""); + out.push( + ` ${live.length} demotion request${live.length === 1 ? "" : "s"} ${c(DIM, "(refusals and fences the dynamic tier would replace)")}` + + (unreached > 0 ? c(DIM, `, ${unreached} more in unreached code`) : "") + + (excluded > 0 ? c(DIM, `, ${excluded} excluded by policy`) : ""), + ); + } + const units = tiers.units.filter((u) => query === undefined || unitMatches(u, query)); + if (query !== undefined && units.length === 0) { + out.push(` no demoted unit matches '${query}'`); + return out.join("\n"); + } + for (const u of units) { + const counts = [ + u.requests > 0 ? `${u.requests} request${u.requests === 1 ? "" : "s"}` : "", + u.sites > 0 ? `${u.sites} site${u.sites === 1 ? "" : "s"} inside` : "", + ] + .filter((s) => s !== "") + .join(", "); + out.push(` ${unitLabel(u, opts.root)} ${c(DIM, `${where(u.loc)} (${counts})`)}`); + if (query === undefined && u.requests === 0) continue; + for (const r of live) { + if (r.unit.kind !== u.kind || r.unit.name !== u.name || r.unit.loc.start !== u.loc.start) + continue; + if (r.unit.loc.file !== u.loc.file) continue; + out.push( + ` ${c(DIM, "<-")} ${r.code} ${r.seed} at ${where(r.loc)}: ${capMessage(r.message)}`, + ); + if (r.enclosing.name !== r.unit.name || r.enclosing.kind !== r.unit.kind) + out.push(` ${c(DIM, "->")} ${unitLabel(r.enclosing, opts.root)} turns dynamic`); + } + } + return out.join("\n"); +} diff --git a/packages/compiler/src/coverage/surface-manifest.ts b/packages/compiler/src/coverage/surface-manifest.ts index 45814cb8b..d82367611 100644 --- a/packages/compiler/src/coverage/surface-manifest.ts +++ b/packages/compiler/src/coverage/surface-manifest.ts @@ -38,7 +38,7 @@ import { InternalCompilerError } from "../errors.js"; * harness asserts refusals match entry codes. renderSurfaceManifest is * byte-deterministic: entries sort by id, keys are emitted in one fixed * order, and the output carries no timestamps or absolute paths. */ -import { FENCE_CODES, UNSUPPORTED } from "../diagnostics/diagnostic.js"; +import { FENCE_CODES, UNSUPPORTED, webAssemblyDiag } from "../diagnostics/diagnostic.js"; import { NODE24_FETCH_COMPAT_PROFILE } from "../compat/fetch-profile.js"; import { SUPPORTED_BUILTIN_MODULES, SUPPORTED_NODE_MODULES } from "../frontend/builtin-modules.js"; import { @@ -275,7 +275,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes status: "static", ...(COLLECTION_ITERATOR_METHODS.has(name) ? { - note: "live native iterators support next(), for...of, and drains; iterator helper methods are refused", + note: "live native iterators support next(), for...of, drains, and the iterator helper methods", } : {}), }); @@ -288,7 +288,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes status: "static", ...(COLLECTION_ITERATOR_METHODS.has(name) ? { - note: "live native iterators support next(), for...of, and drains; iterator helper methods are refused", + note: "live native iterators support next(), for...of, drains, and the iterator helper methods", } : {}), }); @@ -444,6 +444,19 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes } } + // ── the WebAssembly global: every reached use refuses (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 } : {}), + }); + } + // ── fetch/Web platform: one explicit, versioned compatibility profile ─ const fetchTarget = `Node ${NODE24_FETCH_COMPAT_PROFILE.target.node} / ` + diff --git a/packages/compiler/src/coverage/verdict-cache.ts b/packages/compiler/src/coverage/verdict-cache.ts index 841e2b57b..422bf5e4d 100644 --- a/packages/compiler/src/coverage/verdict-cache.ts +++ b/packages/compiler/src/coverage/verdict-cache.ts @@ -14,6 +14,7 @@ import type { AnalyzeOptions, AnalyzeResult } from "../compile-types.js"; import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; import type { LowerStats } from "../frontend/lowering/lowerer.js"; import type { NpmStaticStatus } from "./report.js"; +import type { WasmModuleReport } from "../wasm/discover.js"; import { FrontendInputTracker, frontendInputsStillMatch, @@ -38,6 +39,7 @@ export interface CachedCoverage { unreached?: { stats: LowerStats; diagnostics: ScrDiagnostic[]; runtimeFences?: ScrDiagnostic[] }; divergences?: ScrDiagnostic[]; npmStatic?: NpmStaticStatus[]; + wasmModules?: WasmModuleReport[]; preflightFailed: boolean; } @@ -89,6 +91,9 @@ export function verdictKey( opts: AnalyzeOptions, cache: VerdictCacheOptions, ): string | null { + // The tier report carries source locations the semantic replay does not + // move; explaining analyses always lower. + if (opts.explainDynamic === true) return null; let ffi = ""; if (opts.ffiProfilePath !== undefined) { try { @@ -120,6 +125,10 @@ export function verdictKey( opts.ffiProfilePath ?? "", ffi, external.join("\n"), + // Demotion (stage S6) makes the tier mode part of the verdict. + ...(opts.tier === "static-only" ? ["tier=static-only"] : []), + // The module files' contents are probes; their names are options. + ...(opts.wasm !== undefined && opts.wasm.length > 0 ? [`wasm=${opts.wasm.join("\n")}`] : []), ].join("\0"), ); } @@ -141,6 +150,7 @@ function cacheable(coverage: AnalyzeResult["coverage"]): CachedCoverage | null { if (coverage.unreached !== undefined) out.unreached = coverage.unreached; if (coverage.divergences !== undefined) out.divergences = coverage.divergences; if (coverage.npmStatic !== undefined) out.npmStatic = coverage.npmStatic; + if (coverage.wasmModules !== undefined) out.wasmModules = coverage.wasmModules; return out; } @@ -157,6 +167,7 @@ function restore(cached: CachedCoverage, sources: CachedSource[]): AnalyzeResult if (cached.unreached !== undefined) coverage.unreached = cached.unreached; if (cached.divergences !== undefined) coverage.divergences = cached.divergences; if (cached.npmStatic !== undefined) coverage.npmStatic = cached.npmStatic; + if (cached.wasmModules !== undefined) coverage.wasmModules = cached.wasmModules; return { coverage, sourceTexts }; } diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index eaae1b531..4acd6bc50 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -751,6 +751,20 @@ 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'). */ +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`, + 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)", + }; +} + /** A resolved native addon needs Node's embedding ABI. The migration uses the * underlying native operation through an ordinary C ABI declaration. */ export function nativeAddonDiag(specifier: string, loc: SrcLoc): ScrDiagnostic { @@ -877,13 +891,20 @@ export function unsupportedPackageValueDiag(pkg: string, loc: SrcLoc): ScrDiagno ); } -export function npmStaticRefusalDiag(pkg: string, detail: string, loc: SrcLoc): ScrDiagnostic { +export function npmStaticRefusalDiag( + pkg: string, + detail: string, + loc: SrcLoc, + hint?: string, +): ScrDiagnostic { return { code: "SC2013", message: `cannot compile npm package '${pkg}' statically: ${detail}`, loc, milestone: "later", - hint: "use a dependency with supported source, or replace the unsupported operation; scriptc has no interpreter fallback", + hint: + hint ?? + "use a dependency with supported source, or replace the unsupported operation; scriptc has no interpreter fallback", }; } diff --git a/packages/compiler/src/diagnostics/envelope.ts b/packages/compiler/src/diagnostics/envelope.ts index 39b193dd0..c986fc5b1 100644 --- a/packages/compiler/src/diagnostics/envelope.ts +++ b/packages/compiler/src/diagnostics/envelope.ts @@ -16,6 +16,7 @@ * bumps it. */ import { readFileSync } from "node:fs"; import type { ScrDiagnostic } from "./diagnostic.js"; +import type { WasmModuleReport } from "../wasm/discover.js"; export const DIAGNOSTICS_SCHEMA = "scriptc-diagnostics"; export const DIAGNOSTICS_SCHEMA_VERSION = 1; @@ -81,6 +82,34 @@ export interface DiagnosticGroup { sites: SourcePosition[]; } +export interface TierUnitPosition extends SourcePosition { + kind: "binding" | "field" | "function" | "class" | "module"; + name: string; + owner?: string; +} + +/** `scriptc coverage --explain-dynamic --print=diagnostics`: the tier + * assignment of frontend/tier-analysis.ts with source positions. */ +export interface TiersEnvelope { + mode: "auto" | "static-only"; + statementsDynamic: number; + dynamicFunctions: (SourcePosition & { + name: string; + operation: string; + operationAt: SourcePosition; + statements: number; + })[]; + requests: (SourcePosition & { + unit: TierUnitPosition; + enclosing: TierUnitPosition; + seed: string; + scope: string; + code: string; + message: string; + })[]; + units: (TierUnitPosition & { requests: number; sites: number })[]; +} + export interface DiagnosticsEnvelope { schema: typeof DIAGNOSTICS_SCHEMA; schemaVersion: typeof DIAGNOSTICS_SCHEMA_VERSION; @@ -97,8 +126,10 @@ export interface DiagnosticsEnvelope { stats?: { statementsTotal: number; statementsStatic: number; - /** Always 0: every compiled statement is native. Kept so schema - * version 1 consumers keep reading the same fields. */ + /** The compiled statements (counted in statementsStatic, which covers + * every statement that compiles to native code) that belong to + * dynamic-tier functions: code operating on values whose types are only + * known at runtime. */ statementsDynamic: number; statementsFailed: number; functionsSkipped: number; @@ -106,6 +137,11 @@ export interface DiagnosticsEnvelope { }; /** build only: the produced artifact path. */ artifact?: string; + /** coverage --explain-dynamic only: the report-only tier assignment. */ + tiers?: TiersEnvelope; + /** coverage only: the WebAssembly modules the build can see, with their + * size, validity, proposals and the sources that found them. */ + wasmModules?: WasmModuleReport[]; groups: DiagnosticGroup[]; diagnostics: LocatedDiagnostic[]; } diff --git a/packages/compiler/src/executable/prepare.ts b/packages/compiler/src/executable/prepare.ts index f6012dd2a..d97497c70 100644 --- a/packages/compiler/src/executable/prepare.ts +++ b/packages/compiler/src/executable/prepare.ts @@ -59,6 +59,7 @@ export function prepareExecutableModule( entry.package, entry.detail ?? "its static compilation was refused", fe.npmImportSites.get(entry.package) ?? { file: entryPath, start: 0, end: 0 }, + entry.hint, ), ), ); @@ -69,6 +70,7 @@ export function prepareExecutableModule( lowered = fe.lower({ targetPlatform: buildPlatform, ...(ffi !== null ? { ffiImports: ffi.functions } : {}), + ...(opts.tier !== undefined ? { tierMode: opts.tier } : {}), }); timing("lower"); } catch (e) { diff --git a/packages/compiler/src/frontend/analysis.ts b/packages/compiler/src/frontend/analysis.ts index b38a267fa..43d0aa2bb 100644 --- a/packages/compiler/src/frontend/analysis.ts +++ b/packages/compiler/src/frontend/analysis.ts @@ -58,6 +58,7 @@ export function analyzeWithFrontend( entry.package, entry.detail ?? "its static compilation was refused", fe.npmImportSites.get(entry.package) ?? { file: entryPath, start: 0, end: 0 }, + entry.hint, ), ), ]; @@ -90,6 +91,9 @@ export function analyzeWithFrontend( coverage: true, targetPlatform: buildPlatform, ...(ffi !== null ? { ffiImports: ffi.functions } : {}), + ...(opts.tier !== undefined ? { tierMode: opts.tier } : {}), + ...(opts.explainDynamic === true ? { tierReport: { preflight } } : {}), + ...(opts.wasm !== undefined && opts.wasm.length > 0 ? { wasmFiles: opts.wasm } : {}), }); const provenance = provenanceSources(); return { @@ -109,6 +113,8 @@ export function analyzeWithFrontend( ...(provenance !== null ? { provenance } : {}), ...(lowered.statsByFile ? { statsByFile: lowered.statsByFile } : {}), ...(lowered.provenanceElided ? { provenanceElided: lowered.provenanceElided } : {}), + ...(lowered.tiers !== undefined ? { tiers: lowered.tiers } : {}), + ...(lowered.wasmModules !== undefined ? { wasmModules: lowered.wasmModules } : {}), preflightFailed: false, }, sourceTexts: fe.sourceTexts(), diff --git a/packages/compiler/src/frontend/builtin-modules.ts b/packages/compiler/src/frontend/builtin-modules.ts index b45dce6d7..3a1acb934 100644 --- a/packages/compiler/src/frontend/builtin-modules.ts +++ b/packages/compiler/src/frontend/builtin-modules.ts @@ -42,6 +42,8 @@ export const SUPPORTED_BUILTIN_MODULES = [ "module", "console", "process", + "punycode", + "constants", ] as const; /** Builtins Node itself serves ONLY under the node: prefix — diff --git a/packages/compiler/src/frontend/lowering/builtins/class-values.ts b/packages/compiler/src/frontend/lowering/builtins/class-values.ts new file mode 100644 index 000000000..f24db09cf --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/class-values.ts @@ -0,0 +1,159 @@ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { type ClassInfo } from "../lower-classes.js"; +import { isJsSourceFile } from "../../program.js"; +import { builtinMemberRequireDecl } from "./module-bindings.js"; +import { stripTypeCasts } from "./arguments.js"; +import { streamClassAliasDecl } from "../lower-stream.js"; +import { zlibClassName, zlibStreamModeOf } from "./compression.js"; +import { stdlibGlobalNameOf } from "../surfaces.js"; + +/** A builtin class an expression names at compile time: the error + * classes, node:events EventEmitter, the node:stream classes (each a + * runtime ClassInfo), the node:zlib stream classes (by node_zlib_mode), + * and the Map and Set constructors. `name` is the class's `name` + * property. */ +export type BuiltinClassRef = + | { kind: "error" | "emitter" | "stream"; info: ClassInfo; name: string } + | { kind: "zlib"; mode: number; name: string } + | { kind: "collection"; which: "Map" | "Set"; name: string }; + +/** The builtin class `expr` names: a direct spelling (`EventEmitter`, + * `stream.Readable`, `zlib.Gzip`, `Map`, `globalThis.TypeError`) or a + * `const` binding of one (`const E = EventEmitter`, an alias of an alias, + * an imported alias), through parentheses and type assertions. Null for + * anything else. */ +export function builtinClassRefOf(lowerer: Lowerer, expr: ts.Expression): BuiltinClassRef | null { + const e = stripTypeCasts(expr); + // Checker lookups only: answering must not run the fences and deferred + // diagnostics of a lowered reference (resolveValueSymbol). + let symbol: ts.Symbol | undefined; + if (ts.isIdentifier(e)) { + symbol = lowerer.checker.getSymbolAtLocation(e); + } else if (ts.isPropertyAccessExpression(e) && !e.questionDotToken) { + symbol = lowerer.checker.getSymbolAtLocation(e.name); + } else { + return null; + } + if (!symbol) return null; + const alias = builtinClassAliasOf(lowerer, symbol); + if (alias !== null) return alias; + if (symbol.flags & ts.SymbolFlags.Alias) symbol = lowerer.checker.getAliasedSymbol(symbol); + const which = stdlibGlobalNameOf(lowerer, e); + if (which === "Map" || which === "Set") return { kind: "collection", which, name: which }; + const error = lowerer.builtinErrorInfoOf(symbol); + if (error) return { kind: "error", info: error, name: runtimeClassName(error) }; + const emitter = lowerer.builtinEmitterInfoOf(symbol); + if (emitter) return { kind: "emitter", info: emitter, name: runtimeClassName(emitter) }; + const stream = lowerer.builtinStreamInfoOf(symbol); + if (stream) return { kind: "stream", info: stream, name: runtimeClassName(stream) }; + const mode = zlibStreamModeOf(lowerer, symbol); + if (mode !== null) return { kind: "zlib", mode, name: zlibClassName(mode) }; + return null; +} + +/** The builtin class a `const` binding names (`const E = EventEmitter`, + * `const M = Map`, `const C = zlib.Gzip`, or an alias of such a binding), + * or null. `let` and `var` bindings could be reassigned, and annotated + * bindings give the value another type, so they are not aliases. Resolved from the declaration, so uses lowered before the + * declaration statement (hoisted functions) see the same answer. */ +export function builtinClassAliasOf(lowerer: Lowerer, symbol: ts.Symbol): BuiltinClassRef | null { + const cached = lowerer.builtinClassAliases.get(symbol); + if (cached !== undefined) return cached; + // An import of an exported alias names the exporting binding. + if (symbol.flags & ts.SymbolFlags.Alias) { + lowerer.builtinClassAliases.set(symbol, null); + const target = lowerer.checker.getAliasedSymbol(symbol); + const ref = target === symbol ? null : builtinClassAliasOf(lowerer, target); + lowerer.builtinClassAliases.set(symbol, ref); + return ref; + } + const init = constInitializerOf(lowerer, symbol); + if (init === null) { + lowerer.builtinClassAliases.set(symbol, null); + return null; + } + // Marked first: a cycle through other bindings answers null. + lowerer.builtinClassAliases.set(symbol, null); + const ref = builtinClassRefOf(lowerer, init); + lowerer.builtinClassAliases.set(symbol, ref); + return ref; +} + +/** True when `symbol` is a builtin class alias whose declaration owns no + * runtime slot: new, instanceof, typeof, .name and === lower at each use, + * and other uses report SC1090. In JavaScript files, a binding whose + * initializer has a value (`const E = EventEmitter`, `const M = + * globalThis.Map`, both identity tokens) keeps its slot; only builtin + * module members (`zlib.Gzip`) and copies of slotless aliases own none. + * The `require("events").EventEmitter` and `stream.Writable` spellings + * keep their own declaration handling. */ +export function builtinClassAliasOwnsNoSlot(lowerer: Lowerer, symbol: ts.Symbol): boolean { + if (builtinClassAliasOf(lowerer, symbol) === null) return false; + const binding = + symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol; + const decl = lowerer.checker.valueDeclarationOf(binding); + 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; + let init = decl.initializer; + while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression; + if (init === undefined) return true; + let source: ts.Symbol | undefined; + if (ts.isPropertyAccessExpression(init)) { + if (lowerer.builtinNamespaceModuleOf(init.expression) !== null) return true; + source = lowerer.checker.getSymbolAtLocation(init.name); + } else { + source = lowerer.checker.getSymbolAtLocation(init); + } + return source !== undefined && builtinClassAliasOwnsNoSlot(lowerer, source); +} + +/** True for a `const` declaration that is a builtin class alias owning no + * runtime slot (see builtinClassAliasOwnsNoSlot): both declaration walks + * skip it. */ +export function registerBuiltinClassAlias( + lowerer: Lowerer, + nameNode: ts.Node, + init: ts.Expression | undefined, +): boolean { + if (!ts.isIdentifier(nameNode) || init === undefined) return false; + const symbol = lowerer.checker.getSymbolAtLocation(nameNode); + return symbol !== undefined && builtinClassAliasOwnsNoSlot(lowerer, symbol); +} + +/** True when two references name the same builtin class. */ +export function sameBuiltinClass(a: BuiltinClassRef, b: BuiltinClassRef): boolean { + if (a.kind === "zlib") return b.kind === "zlib" && a.mode === b.mode; + if (a.kind === "collection") return b.kind === "collection" && a.which === b.which; + return b.kind === a.kind && b.info === a.info; +} + +/** The initializer of an unannotated `const` binding whose initializer + * can name a class (an identifier or a property access), or null. */ +function constInitializerOf(lowerer: Lowerer, symbol: ts.Symbol): ts.Expression | null { + if ((symbol.flags & ts.SymbolFlags.BlockScopedVariable) === 0) return null; + const decl = lowerer.checker.valueDeclarationOf(symbol); + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + !ts.isIdentifier(decl.name) || + decl.type !== undefined + ) { + return null; + } + const list = decl.parent; + if (!ts.isVariableDeclarationList(list) || (list.flags & ts.NodeFlags.Const) === 0) return null; + let init = decl.initializer; + while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression; + if (init === undefined) return null; + if (ts.isIdentifier(init)) return init; + if (ts.isPropertyAccessExpression(init) && !init.questionDotToken) return init; + return null; +} + +/** Node's constructor name of a runtime class (`%TypeError` → TypeError). */ +function runtimeClassName(info: ClassInfo): string { + return info.def.name.startsWith("%") ? info.def.name.slice(1) : info.def.name; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/compression.ts b/packages/compiler/src/frontend/lowering/builtins/compression.ts index c9553c2f9..6de345dfe 100644 --- a/packages/compiler/src/frontend/lowering/builtins/compression.ts +++ b/packages/compiler/src/frontend/lowering/builtins/compression.ts @@ -132,8 +132,8 @@ export function zlibStreamModeOf( symbol: ts.Symbol | null | undefined, ): number | null { if (!symbol) return null; - const aliasMode = zlibClassAliasModeOf(lowerer, symbol, 0); - if (aliasMode !== undefined) return aliasMode; + const alias = lowerer.builtinClassAliasOf(symbol); + 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); @@ -499,80 +499,12 @@ export function lowerZlibModuleCall( throw new InternalCompilerError(`unhandled lowered zlib member ${bi.member}`); } -/** The zlib stream class an expression names (`zlib.Gzip`, an imported - * `Gzip`, a `const C = zlib.Gzip` alias), as its node_zlib_mode, or null. */ -export function zlibClassModeOfExpr(lowerer: Lowerer, expr: ts.Expression): number | null { - let e = expr; - while (ts.isParenthesizedExpression(e)) e = e.expression; - if (ts.isIdentifier(e)) return zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(e)); - if (ts.isPropertyAccessExpression(e) && !e.questionDotToken) { - return zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(e.name)); - } - return null; -} - /** The class name of a zlib stream mode (Node's constructor name). */ export function zlibClassName(mode: number): string { for (const [name, m] of ZLIB_STREAM_MODES) if (m === mode) return name; return "Zlib"; } -/** The mode of a `const C = zlib.Gzip` binding (or an alias of such a - * binding), null when the symbol is a const binding of something else, - * undefined when it is not a const binding. Resolved from the declaration, - * so uses lowered before the declaration statement see the same answer. */ -function zlibClassAliasModeOf( - lowerer: Lowerer, - symbol: ts.Symbol, - depth: number, -): number | null | undefined { - const cached = lowerer.zlibClassAliases.get(symbol); - if (cached !== undefined) return cached; - if ((symbol.flags & ts.SymbolFlags.BlockScopedVariable) === 0) return undefined; - const decl = lowerer.checker.valueDeclarationOf(symbol); - if (decl === undefined || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name)) { - return undefined; - } - // const only: a var or let binding could be reassigned - const list = decl.parent; - if (!ts.isVariableDeclarationList(list) || (list.flags & ts.NodeFlags.Const) === 0) { - return undefined; - } - let init = decl.initializer; - while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression; - if (init === undefined || depth > 16) return null; - let mode: number | null = null; - if (ts.isIdentifier(init)) { - const next = lowerer.checker.getSymbolAtLocation(init); - if (next !== undefined) { - const nested = zlibClassAliasModeOf(lowerer, next, depth + 1); - mode = nested === undefined ? zlibStreamModeOf(lowerer, next) : nested; - } - } else if (ts.isPropertyAccessExpression(init) && !init.questionDotToken) { - mode = zlibStreamModeOf(lowerer, lowerer.checker.getSymbolAtLocation(init.name)); - } - if (mode !== null) lowerer.zlibClassAliases.set(symbol, mode); - return mode; -} - -/** True for `const C = zlib.Gzip` (or an alias of an alias): a - * compile-time zlib class alias that owns no runtime slot, so the - * statement lowering skips it. */ -export function registerZlibClassAlias( - lowerer: Lowerer, - nameNode: ts.Node, - init: ts.Expression | undefined, -): boolean { - if (!ts.isIdentifier(nameNode) || init === undefined) return false; - const symbol = lowerer.checker.getSymbolAtLocation(nameNode); - return symbol !== undefined && zlibClassAliasOf(lowerer, symbol) !== null; -} - -/** The mode of a `const C = zlib.Gzip` alias symbol, or null. */ -export function zlibClassAliasOf(lowerer: Lowerer, symbol: ts.Symbol): number | null { - return zlibClassAliasModeOf(lowerer, symbol, 0) ?? null; -} - /** `value instanceof zlib.Gzip` (any spelling of the class, aliases * included). zlib streams are runtime Transforms: a Transform operand asks * the runtime for the stream's mode, and an operand typed as one of diff --git a/packages/compiler/src/frontend/lowering/builtins/crypto.ts b/packages/compiler/src/frontend/lowering/builtins/crypto.ts index a492a0da7..bfac6128f 100644 --- a/packages/compiler/src/frontend/lowering/builtins/crypto.ts +++ b/packages/compiler/src/frontend/lowering/builtins/crypto.ts @@ -1,6 +1,6 @@ import { lowerBuiltinByteInput, errorFirstBytesCallback, errorFirstCallback } from "./arguments.js"; import { isKeyObjectValue } from "./key-object.js"; -import { boolLit, dynUndefinedExpr, varRef } from "../../../ir/build.js"; +import { boolLit, dynUndefinedExpr, nodeThrowExpr, varRef } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer, own } from "../lowerer.js"; import { locOf } from "../../program.js"; @@ -941,9 +941,27 @@ export function lowerCryptoHashMethodCall( if (call.questionDotToken || access.questionDotToken) return null; const receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); if (receiverType?.kind !== "cryptoHash" && receiverType?.kind !== "cryptoHmac") return null; - if (!lowerer.isStdlibMember(access)) return null; const name = access.name.text; const loc = locOf(call); + // Hmac has no copy(): Node reads the member as undefined and throws a + // TypeError once the arguments are evaluated. + if (receiverType.kind === "cryptoHmac" && name === "copy") { + if (call.arguments.some(ts.isSpreadElement)) + lowerer.noLowering("Hmac.copy with spread arguments", call); + const evaluated = [access.expression, ...call.arguments].map((node): IrStmt => ({ + kind: "exprStmt", + expr: lowerer.lowerExpr(node), + loc, + })); + return { + kind: "seqExpr", + stmts: evaluated, + result: nodeThrowExpr(1, "", `${access.getText()} is not a function`, DYN, loc), + type: DYN, + loc, + }; + } + if (!lowerer.isStdlibMember(access)) return null; const receiver = (): IrExpr => lowerer.lowerExprExpecting(access.expression, receiverType); const hash = receiverType.kind === "cryptoHash"; const label = hash ? "Hash" : "Hmac"; diff --git a/packages/compiler/src/frontend/lowering/builtins/json.ts b/packages/compiler/src/frontend/lowering/builtins/json.ts index a73c9d5e7..013b16e7c 100644 --- a/packages/compiler/src/frontend/lowering/builtins/json.ts +++ b/packages/compiler/src/frontend/lowering/builtins/json.ts @@ -8,6 +8,10 @@ import { isJsonStringifyDynamicType, BOOL, DYN, + type IrType, + classDynViewSupported, + isClassOwnEnumerableFieldName, + isDynTypedRefType, type IrExpr, STRING, type SrcLoc, @@ -157,6 +161,16 @@ export function lowerJsonMethodCall( (id) => lowerer.unions.get(id), ) ) { + const unreadable = classFieldWithoutJsonView(lowerer, value.type, new Set()); + if (unreadable !== null) { + lowerer.unsupported( + "SC1090", + argNode, + `JSON.stringify of '${lowerer.fmt(value.type)}' values (field '${unreadable.field}' of class ` + + `'${unreadable.className}' has type '${lowerer.fmt(unreadable.type)}', which the runtime JSON ` + + `traversal cannot ${unreadable.reason})`, + ); + } // Keep the original containers live while the runtime visits them: // a toJSON callback can mutate a later field, and shared/cyclic // references must retain their identities throughout traversal. @@ -276,6 +290,100 @@ function lowerOptionalStringifyRoot( return { kind: "call", callee: helper, args: [value], type: resultT, loc }; } +/** True when a class field type holds a RegExp or a Promise outside a + * nested class instance (whose own fields are checked separately): Node + * writes both as {}, and the runtime JSON traversal has no writer for them. */ +function holdsUnwritable(lowerer: Lowerer, t: IrType, visiting: Set): boolean { + switch (t.kind) { + case "regex": + case "promise": + return true; + case "array": + return holdsUnwritable(lowerer, t.elem, visiting); + case "record": { + if (visiting.has(t.shapeId)) return false; + visiting.add(t.shapeId); + const shape = lowerer.shapes.get(t.shapeId); + return ( + (shape?.fields ?? []).some((field) => holdsUnwritable(lowerer, field.type, visiting)) || + (shape?.indexValue !== undefined && holdsUnwritable(lowerer, shape.indexValue, visiting)) + ); + } + case "union": { + if (visiting.has(t.unionId)) return false; + visiting.add(t.unionId); + return (lowerer.unions.get(t.unionId)?.arms ?? []).some((arm) => + holdsUnwritable(lowerer, arm, visiting), + ); + } + default: + return false; + } +} + +/** The first class field reachable from a stringify input that the class's + * checked-dynamic property view cannot represent, or that holds a RegExp or + * a Promise, which the runtime JSON traversal cannot write. The traversal + * reads class instances through that view, so such a class is refused at + * compile time instead of throwing when the view is built or written. */ +function classFieldWithoutJsonView( + lowerer: Lowerer, + t: IrType, + visiting: Set, +): { className: string; field: string; type: IrType; reason: "read" | "write" } | null { + const getRecord = (id: string) => lowerer.shapes.get(id); + const getUnion = (id: string) => lowerer.unions.get(id); + switch (t.kind) { + case "array": + return classFieldWithoutJsonView(lowerer, t.elem, visiting); + case "record": { + if (visiting.has(t.shapeId)) return null; + visiting.add(t.shapeId); + const shape = getRecord(t.shapeId); + for (const field of shape?.fields ?? []) { + const found = classFieldWithoutJsonView(lowerer, field.type, visiting); + if (found) return found; + } + return shape?.indexValue + ? classFieldWithoutJsonView(lowerer, shape.indexValue, visiting) + : null; + } + case "union": { + if (visiting.has(t.unionId)) return null; + visiting.add(t.unionId); + for (const arm of getUnion(t.unionId)?.arms ?? []) { + const found = classFieldWithoutJsonView(lowerer, arm, visiting); + if (found) return found; + } + return null; + } + case "object": { + if (!isDynTypedRefType(t) || visiting.has(t.className)) return null; + visiting.add(t.className); + const info = lowerer.classes.get(t.className); + for (const field of info?.def.fields ?? []) { + if (!isClassOwnEnumerableFieldName(field.name)) continue; + const unreadable = !classDynViewSupported([field], getRecord, getUnion); + if (unreadable || holdsUnwritable(lowerer, field.type, new Set())) { + const className = + info?.def.jsName ?? info?.decl?.name?.text ?? t.className.replace(/^%/, ""); + return { + className, + field: field.name, + type: field.type, + reason: unreadable ? "read" : "write", + }; + } + const found = classFieldWithoutJsonView(lowerer, field.type, visiting); + if (found) return found; + } + return null; + } + default: + return null; + } +} + /** Only explicit null/undefined select the no-callback path. */ function jsonNullishArgument(lowerer: Lowerer, node: ts.Expression): boolean { if (ts.isParenthesizedExpression(node)) return jsonNullishArgument(lowerer, node.expression); diff --git a/packages/compiler/src/frontend/lowering/builtins/numbers.ts b/packages/compiler/src/frontend/lowering/builtins/numbers.ts index 08d6cb349..4f86e6f74 100644 --- a/packages/compiler/src/frontend/lowering/builtins/numbers.ts +++ b/packages/compiler/src/frontend/lowering/builtins/numbers.ts @@ -2,7 +2,7 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer, own } from "../lowerer.js"; import { lowerCheckedPredicateValue } from "../lower-builtin-values.js"; import { locOf } from "../../program.js"; -import { BOOL, F64, type IrExpr, type IrLibFn } from "../../../ir/ir.js"; +import { BOOL, F64, STRING, type IrExpr, type IrLibFn } from "../../../ir/ir.js"; import { lowerBuiltinValuePreservingUndefined, lowerOptionalNumberPredicate } from "./arguments.js"; import { tryLowerExpression } from "../expressions/try-lower-expression.js"; import { lowerStaticNumberGlobal } from "../lower-calls.js"; @@ -88,6 +88,22 @@ export function lowerNumberStaticCall( const parsed = lowerStaticNumberGlobal(lowerer, member, call, call.arguments, loc); if (parsed !== null) return parsed; } + // A checked-dynamic argument (`Number.parseInt(env.FORCE_COLOR, 10)` + // over a JavaScript dictionary read): the parsers' own ToString, + // exactly the global parseFloat's dyn form. + if (probed?.type.kind === "dyn") { + const text: IrExpr = { + kind: "libCall", + fn: "dyn.toStringCoerce", + args: [probed], + type: STRING, + loc, + }; + if (member === "parseFloat") + return { kind: "libCall", fn: "num.parseFloat", args: [text], type: F64, loc }; + const radix = lowerer.lowerExprExpecting(call.arguments[1]!, F64); + return { kind: "libCall", fn: "num.parseInt", args: [text, radix], type: F64, loc }; + } const argText = probed !== null ? lowerer.fmt(probed.type) diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index 97bf1e963..94c2cdba7 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -233,6 +233,41 @@ export function lowerProcessStreamProperty( * read in source order). Method members referenced without a call are * rejected specifically. Null for non-process receivers (the chain keeps * trying other property lowerings). */ +/** The value of a DATA member of the process global taken off by + * destructuring (`const { env, argv } = process` in JavaScript, the + * supports-color opening): the same values the spelled `process.env` / + * `process.argv` reads produce, typed by the binding. Null for members + * this does not cover (methods keep the destructuring fence). */ +export function processDataMemberValue( + lowerer: Lowerer, + member: string, + binding: ts.Node, + loc: SrcLoc, +): IrExpr | null { + switch (member) { + case "env": { + const mapped = lowerer.mapTypeOf(lowerer.typeOf(binding)); + if (mapped?.kind !== "record") return null; + const helper = envSnapshotHelper(lowerer, mapped.shapeId, loc); + return helper === null ? null : { kind: "call", callee: helper, args: [], type: mapped, loc }; + } + case "argv": + return { kind: "libCall", fn: "process.argv", args: [], type: arrayOf(STRING), loc }; + case "platform": + return { kind: "libCall", fn: "process.platform", args: [], type: STRING, loc }; + case "arch": + return { kind: "libCall", fn: "process.arch", args: [], type: STRING, loc }; + case "pid": + return { kind: "libCall", fn: "process.pid", args: [], type: F64, loc }; + case "execPath": + return { kind: "libCall", fn: "process.execPath", args: [], type: STRING, loc }; + case "versions": + return { kind: "libCall", fn: "process.versions", args: [], type: DYN, loc }; + default: + return null; + } +} + export function lowerProcessProperty( lowerer: Lowerer, expr: ts.PropertyAccessExpression, diff --git a/packages/compiler/src/frontend/lowering/builtins/webassembly.ts b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts new file mode 100644 index 000000000..dc61853f8 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts @@ -0,0 +1,132 @@ +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 { 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. */ + +/** Strips the spellings that leave a value unchanged: parentheses, type + * assertions, `satisfies` and non-null assertions. */ +function bare(expr: ts.Expression): ts.Expression { + for (;;) { + if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) expr = expr.expression; + else if (ts.isAsExpression(expr) || ts.isTypeAssertion(expr)) expr = expr.expression; + else if (ts.isSatisfiesExpression(expr)) expr = expr.expression; + else return expr; + } +} + +/** The global object itself, as `globalThis` or `global` spells it. */ +function isGlobalThis(lowerer: Lowerer, expr: ts.Expression): boolean { + const node = bare(expr); + return ( + ts.isIdentifier(node) && + (node.text === "globalThis" || node.text === "global") && + stdlibGlobalNameOf(lowerer, node) === "globalThis" + ); +} + +/** THE WebAssembly namespace value: `WebAssembly` or `globalThis.WebAssembly` + * (and the global object reached through a cast, as in + * `(globalThis as any).WebAssembly`). */ +function isWebAssemblyGlobal(lowerer: Lowerer, expr: ts.Expression): boolean { + expr = bare(expr); + if (ts.isIdentifier(expr)) { + return expr.text === "WebAssembly" && stdlibGlobalNameOf(lowerer, expr) === "WebAssembly"; + } + if (ts.isPropertyAccessExpression(expr)) { + return expr.name.text === "WebAssembly" && isGlobalThis(lowerer, expr.expression); + } + if (ts.isElementAccessExpression(expr)) { + const key = expr.argumentExpression; + return ( + (ts.isStringLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key)) && + key.text === "WebAssembly" && + isGlobalThis(lowerer, expr.expression) + ); + } + return false; +} + +/** The surface a WebAssembly API expression names ('WebAssembly', + * 'globalThis.WebAssembly', 'WebAssembly.compile'), or null. A cheap + * spelling check runs first: almost every expression is rejected without a + * checker query. */ +function webAssemblySurface(lowerer: Lowerer, expr: ts.Expression): string | null { + // `globalThis as unknown as { WebAssembly: ... }`: the cast's target type + // names the member, and the value it checks is the global object's + // WebAssembly (the spelling pi's codemode uses). + if ( + (ts.isAsExpression(expr) || ts.isTypeAssertion(expr)) && + isGlobalThis(lowerer, expr.expression) + ) { + const target = lowerer.checker.getTypeAtLocation(expr.type); + return lowerer.checker.getPropertyOfType(target, "WebAssembly") !== undefined + ? "globalThis.WebAssembly" + : null; + } + const node = bare(expr); + if (ts.isIdentifier(node)) { + return node.text === "WebAssembly" && isWebAssemblyGlobal(lowerer, node) ? "WebAssembly" : null; + } + if (ts.isPropertyAccessExpression(node)) { + if (node.name.text === "WebAssembly") { + return isWebAssemblyGlobal(lowerer, node) ? "globalThis.WebAssembly" : null; + } + // WebAssembly., and members of members + // (WebAssembly.Module.customSections). + const names = [node.name.text]; + let receiver = bare(node.expression); + while (ts.isPropertyAccessExpression(receiver) && receiver.name.text !== "WebAssembly") { + names.unshift(receiver.name.text); + receiver = bare(receiver.expression); + } + const spelled = + (ts.isIdentifier(receiver) && receiver.text === "WebAssembly") || + (ts.isPropertyAccessExpression(receiver) && receiver.name.text === "WebAssembly"); + return spelled && isWebAssemblyGlobal(lowerer, receiver) + ? `WebAssembly.${names.join(".")}` + : null; + } + if (ts.isElementAccessExpression(node)) { + return isWebAssemblyGlobal(lowerer, node) ? "globalThis.WebAssembly" : null; + } + return null; +} + +/** Fences a reached use of the WebAssembly API. `prefix` qualifies a + * construction ('new '). Returns normally when `expr` is anything else. */ +export function refuseWebAssemblyUse(lowerer: Lowerer, expr: ts.Expression, prefix = ""): void { + const surface = webAssemblySurface(lowerer, expr); + if (surface === null) return; + lowerer.pushDiag(webAssemblyDiag(`${prefix}${surface}`, locOf(expr))); + throw new PoisonError(); +} + +/** `typeof WebAssembly` is "object" and `typeof WebAssembly.` is + * "function" for every member the namespace declares, as in Node. The + * operand is an effect-free global read, so nothing else evaluates. */ +export function lowerWebAssemblyTypeof(lowerer: Lowerer, operand: ts.Expression): IrExpr | null { + const surface = webAssemblySurface(lowerer, operand); + if (surface === null) return null; + const loc = locOf(operand); + if (surface === "WebAssembly" || surface === "globalThis.WebAssembly") { + return { kind: "strLit", value: "object", type: STRING, loc }; + } + const t = lowerer.typeOf(operand); + if ( + lowerer.checker.getCallSignatures(t).length > 0 || + lowerer.checker.getConstructSignatures(t).length > 0 + ) { + return { kind: "strLit", value: "function", type: STRING, loc }; + } + return null; +} diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 8e30e8b82..c1cc72021 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -503,6 +503,24 @@ export function requireExactArityValue( ? lowerer.mapTypeOf(lowerer.typeOf(contextual)) : null; if (mapped && typeEquals(mapped, funcType)) return; + // An object-literal METHOD has no contextual type of its own: the + // literal's contextual type names the member slot it fills. An + // overloaded member's slot holds its erased function, which spells the + // completed parameters of an implementation like `m(a?: string)`. + if ( + contextual === null && + ts.isMethodDeclaration(blame) && + blame.parent !== undefined && + ts.isObjectLiteralExpression(blame.parent) && + (ts.isIdentifier(blame.name) || ts.isStringLiteral(blame.name)) + ) { + const literalType = lowerer.checker.getContextualType(blame.parent); + const member = literalType + ? lowerer.checker.getPropertyOfType(literalType, blame.name.text) + : undefined; + const slot = member ? lowerer.memberSlotTypeOf(lowerer.checker.getTypeOfSymbol(member)) : null; + if (slot && typeEquals(slot, funcType)) return; + } // A union-typed slot (`runner || defaultRunner` under a // `CommandRunner | undefined` context): the value can only inhabit // the union's one func arm — judge by it. diff --git a/packages/compiler/src/frontend/lowering/cjs-exports-object.ts b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts new file mode 100644 index 000000000..d72792ae7 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts @@ -0,0 +1,430 @@ +/* A CommonJS module's own exports object, seen from inside the module. + * + * The compiled module system has no reflective exports object: each export + * is a module global (collectGlobals) or alias plumbing to a declaration. + * Inside the module that is enough for + * + * - member reads and member calls (`exports.f`, `exports.f(x)`, + * `module.exports.f(x)`): they are the member's export global, the + * storage every `exports.f =` statement assigns; + * - identity comparisons of the object itself (`root === module.exports`, + * `exports === module.exports`): while the module never replaces + * `module.exports` and never rebinds `exports`, both name the module's + * original exports object, which top-level `this` already represents + * (lowerCjsTopLevelThis). `typeof` and truthiness keep their existing + * lowerings. + * + * Every other use of the object as a value (passing it to a function, + * storing it, reading a member through an alias) would need a real object + * whose properties are the exports; those uses keep a named refusal. The + * dynamic CommonJS module object is the two-tier design's S9 stage. */ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; +import { + cjsExportAssignmentOf, + cjsExportTargetLiteral, + isCjsJsFile, + isModuleExportsAccess, + isRequireStatement, + locOf, +} from "../program.js"; +import { isCjsModuleGlobal } from "./lower-node-module.js"; +import { DYN, type IrExpr, type IrGlobal } from "../../ir/ir.js"; + +/** True for the CommonJS wrapper's `exports` binding (not shadowed by any + * 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; + const symbol = lowerer.checker.getSymbolAtLocation(node); + const decls = symbol ? lowerer.checker.declarationsOf(symbol) : []; + return !decls.some( + (d) => + !d.getSourceFile().isDeclarationFile && + (ts.isVariableDeclaration(d) || + ts.isParameter(d) || + ts.isBindingElement(d) || + ts.isFunctionDeclaration(d) || + ts.isClassDeclaration(d) || + ts.isImportSpecifier(d) || + ts.isNamespaceImport(d) || + ts.isImportEqualsDeclaration(d)), + ); +} + +/** `module.exports` over the CommonJS `module` global in a CommonJS file. */ +export function isCjsModuleExports(lowerer: Lowerer, node: ts.Expression): boolean { + return ( + isModuleExportsAccess(node) && + isCjsModuleGlobal(lowerer, node.expression) && + isCjsJsFile(node.getSourceFile(), lowerer.program) + ); +} + +/** The export global behind a same-module member access `exports.` / + * `module.exports.`, or null (identifier-valued members are alias + * plumbing with no storage, and other receivers are not the exports + * object). */ +export function cjsInFileExportGlobalOf( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, +): IrGlobal | null { + if (access.questionDotToken || !ts.isIdentifier(access.name)) return null; + const sf = access.getSourceFile(); + const recv = + isModuleExportsAccess(access.expression) || + (ts.isIdentifier(access.expression) && + access.expression.text === "exports" && + !lowerer.resolveLocal(access.expression) && + !lowerer.globalOf(access.expression)); + if (!recv || !isCjsJsFile(sf, lowerer.program)) return null; + const sym = + lowerer.checker.getSymbolAtLocation(access.name) ?? + lowerer.cjsModuleExportSymbol(sf, access.name.text); + const resolved = + sym !== undefined && sym.flags & ts.SymbolFlags.Alias + ? lowerer.checker.getAliasedSymbol(sym) + : sym; + return ( + (sym ? lowerer.globalsBySymbol.get(sym) : undefined) ?? + (resolved ? lowerer.globalsBySymbol.get(resolved) : undefined) ?? + null + ); +} + +/** Whether `node` is an operand of an equality operator: only its identity + * is observed. */ +function identityUse(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 || !ts.isBinaryExpression(parent)) return false; + const op = parent.operatorToken.kind; + return ( + (op === ts.SyntaxKind.EqualsEqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsEqualsToken || + op === ts.SyntaxKind.EqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsToken) && + (parent.left === child || parent.right === child) + ); +} + +/** Uses whose answer does not need the object: `typeof`, `void`, and + * truthiness tests (their existing lowerings answer them). */ +function answeredWithoutObject(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.isTypeOfExpression(parent) || ts.isVoidExpression(parent)) return true; + return truthinessOnly(child as ts.Expression); +} + +function truthinessOnly(node: ts.Expression): boolean { + let child: ts.Node = node; + for (let parent = node.parent; parent !== undefined; child = parent, parent = parent.parent) { + if (ts.isParenthesizedExpression(parent)) continue; + if (ts.isPrefixUnaryExpression(parent)) + return parent.operator === ts.SyntaxKind.ExclamationToken; + if ( + ts.isBinaryExpression(parent) && + (parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || + parent.operatorToken.kind === ts.SyntaxKind.BarBarToken) + ) + continue; + 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; + return false; + } + return false; +} + +/** Which of the module's exports bindings the file ever assigns (any + * nesting, compound operators included), cached per file on the lowerer + * (Lowerer.cjsExportsAssignments). */ +function exportsAssignments( + lowerer: Lowerer, + sf: ts.SourceFile, +): { exports: boolean; moduleExports: boolean } { + const cached = lowerer.cjsExportsAssignments.get(sf); + if (cached !== undefined) return cached; + const out = { exports: false, moduleExports: false }; + lowerer.cjsExportsAssignments.set(sf, out); + ts.walkPreorder(sf, (node) => { + // A fresh `exports = module.exports = {}` reset leaves both bindings + // naming one (new, empty) exports object for the rest of the module. + if (ts.isExpressionStatement(node) && isFreshExportsReset(lowerer, node)) return "skip"; + if ( + !ts.isBinaryExpression(node) || + node.operatorToken.kind < ts.SyntaxKind.FirstAssignment || + node.operatorToken.kind > ts.SyntaxKind.LastAssignment + ) + return undefined; + let target = node.left; + while (ts.isParenthesizedExpression(target)) target = target.expression; + if (isCjsModuleExports(lowerer, target)) out.moduleExports = true; + if (isCjsExportsIdentifier(lowerer, target)) out.exports = true; + return undefined; + }); + return out; +} + +/** Whether `node` still names the module's ORIGINAL exports object wherever + * 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()); + if (assigned.exports) return false; + return ts.isIdentifier(node) || !assigned.moduleExports; +} + +/** The uses this module answers with the original exports object: identity + * comparisons and `typeof` of either spelling, and truthiness or `void` of + * `exports` (`module.exports` truthiness keeps its existing fold). */ +function objectAnsweredUse(node: ts.Expression): boolean { + if (identityUse(node)) return true; + let parent = node.parent; + while (parent !== undefined && ts.isParenthesizedExpression(parent)) parent = parent.parent; + if (parent !== undefined && ts.isTypeOfExpression(parent)) return true; + return ts.isIdentifier(node) && answeredWithoutObject(node); +} + +/** Whether `node` (`exports` or `module.exports`) is a use of the module's + * exports object as a VALUE (not a member access receiver, not an + * assignment target). */ +export function isCjsExportsObjectValue(lowerer: Lowerer, node: ts.Expression): boolean { + if (!isCjsExportsIdentifier(lowerer, node) && !isCjsModuleExports(lowerer, node)) return false; + let outer: ts.Node = node; + while (outer.parent !== undefined && ts.isParenthesizedExpression(outer.parent)) + outer = outer.parent; + const parent = outer.parent; + if (parent === undefined) return false; + if ( + (ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) && + parent.expression === outer + ) + return false; + if ( + ts.isBinaryExpression(parent) && + parent.left === outer && + parent.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + parent.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + return false; + return true; +} + +/** Files whose exports object is observed by identity somewhere: their top- + * level `this` object must exist (lowerFileInit creates it). */ +export function usesCjsExportsIdentity(lowerer: Lowerer, sf: ts.SourceFile): boolean { + if (!isCjsJsFile(sf, lowerer.program)) return false; + let found = false; + ts.walkPreorder(sf, (node) => { + if ( + (ts.isIdentifier(node) || ts.isPropertyAccessExpression(node)) && + isCjsExportsObjectValue(lowerer, node) && + objectAnsweredUse(node) && + namesOriginalExports(lowerer, node) + ) { + found = true; + return "stop"; + } + return undefined; + }); + return found; +} + +/** `exports` / `module.exports` used as a value inside its own module: the + * original exports object for identity, typeof and truthiness, or a named + * 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(); + // 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). + if (!ts.isIdentifier(node) && exportsAssignments(lowerer, sf).moduleExports) return null; + const spelled = ts.isIdentifier(node) ? "exports" : "module.exports"; + const original = lowerer.cjsExportsThis.get(sf); + if (objectAnsweredUse(node) && original !== undefined && namesOriginalExports(lowerer, node)) { + return { kind: "varRef", localId: original.id, type: DYN, loc: locOf(node) }; + } + // `module.exports` truthiness keeps its existing fold (and anything the + // object is not needed for keeps whatever answered it before). + if (answeredWithoutObject(node)) return null; + lowerer.unsupported( + "SC1090", + node, + identityUse(node) + ? `comparing '${spelled}' in a module that replaces module.exports or rebinds exports (the compiled exports are a static table, not an object with an identity)` + : `'${spelled}' as a value other than a member access, comparison, typeof or truthiness test (the compiled exports are a static table, not an object: read ${spelled}.)`, + ); +} + +/** `exports = module.exports = {}` (either order) as a top-level statement + * that runs before anything could observe or extend the original exports + * object: only directives, require statements and function declarations + * precede it, and the module assigns neither binding anywhere else. Node + * swaps the original empty exports object for another empty one that both + * bindings name; the compiled module's static exports start out exactly + * the same, so the statement is a no-op (semver's internal/re.js). */ +export function isFreshExportsReset(lowerer: Lowerer, stmt: ts.ExpressionStatement): boolean { + const sf = stmt.parent; + if (!ts.isSourceFile(sf) || !isCjsJsFile(sf, lowerer.program)) return false; + let e: ts.Expression = stmt.expression; + const targets: ts.Expression[] = []; + for (;;) { + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.EqualsToken) break; + targets.push(e.left); + e = e.right; + } + if (!ts.isObjectLiteralExpression(e) || e.properties.length !== 0) return false; + if (targets.length !== 2) return false; + const hasExports = targets.some((t) => isCjsExportsIdentifier(lowerer, t)); + const hasModuleExports = targets.some((t) => isCjsModuleExports(lowerer, t)); + if (!hasExports || !hasModuleExports) return false; + for (const prior of sf.statements) { + if (prior === stmt) break; + if (ts.isFunctionDeclaration(prior) || isRequireStatement(prior)) continue; + if (ts.isExpressionStatement(prior) && ts.isStringLiteral(prior.expression)) continue; + return false; + } + let others = 0; + ts.walkPreorder(sf, (node) => { + if ( + !ts.isBinaryExpression(node) || + node.operatorToken.kind < ts.SyntaxKind.FirstAssignment || + node.operatorToken.kind > ts.SyntaxKind.LastAssignment + ) + return undefined; + let target = node.left; + while (ts.isParenthesizedExpression(target)) target = target.expression; + if (isCjsModuleExports(lowerer, target) || isCjsExportsIdentifier(lowerer, target)) others++; + return undefined; + }); + return others === 2; +} + +/** The export global a WRITE `exports. = v` / `module.exports. = + * v` updates when it is not a top-level export statement (inside a + * function, or in value position): only while the module keeps its + * original exports object, which is then the object every requirer reads + * the member from. */ +export function cjsExportMemberWriteGlobalOf( + lowerer: Lowerer, + target: ts.PropertyAccessExpression, +): IrGlobal | null { + const sf = target.getSourceFile(); + if (!isCjsJsFile(sf, lowerer.program)) return null; + const assigned = exportsAssignments(lowerer, sf); + if (assigned.exports || assigned.moduleExports) return null; + return cjsInFileExportGlobalOf(lowerer, target); +} + +/** Whether a function body reads `this` (arrow functions inherit it; other + * nested functions bind their own). */ +function functionUsesThis(fn: ts.FunctionLikeDeclaration): boolean { + if (fn.body === undefined) return false; + let found = false; + ts.walkPreorder(fn.body, (node) => { + if (node !== fn.body && ts.isFunctionLike(node) && !ts.isArrowFunction(node)) return "skip"; + if (node.kind === ts.SyntaxKind.ThisKeyword) { + found = true; + return "stop"; + } + return undefined; + }); + return found; +} + +/** A function declaration, or a variable initialized with a function or + * arrow expression, that never reads `this`. */ +function symbolIsThisFreeFunction(lowerer: Lowerer, sym: ts.Symbol | undefined): boolean { + const decls = sym ? lowerer.checker.declarationsOf(sym) : []; + const fn = decls.find(ts.isFunctionDeclaration); + if (fn !== undefined) return !functionUsesThis(fn); + const v = decls.find(ts.isVariableDeclaration); + let init = v?.initializer; + while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression; + return ( + init !== undefined && + (ts.isFunctionExpression(init) || ts.isArrowFunction(init)) && + !functionUsesThis(init) + ); +} + +/** True when every value the module assigns to its export `name` is a + * function that never reads `this`. A call through the exports object + * (`exports.f(x)`) binds `this` to that object, which the static export + * table cannot supply, so only these calls lower; the others keep a fence. */ +export function cjsExportCallIgnoresThis( + lowerer: Lowerer, + sf: ts.SourceFile, + name: string, + depth = 0, +): boolean { + if (depth > 16) return false; + const values: ts.Expression[] = []; + ts.walkPreorder(sf, (node) => { + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(node.left) && + node.left.name.text === name && + (isCjsExportsIdentifier(lowerer, node.left.expression) || + isCjsModuleExports(lowerer, node.left.expression)) + ) { + values.push(node.right); + } + return undefined; + }); + // Entries of a `module.exports = { ... }` table (direct, or a const + // literal the statement names). + let methodEntry: ts.MethodDeclaration | null = null; + let shorthandOnly = false; + for (const stmt of sf.statements) { + const cjs = cjsExportAssignmentOf(stmt); + if (cjs?.kind !== "table") continue; + const table = cjs.obj ?? cjsExportTargetLiteral(cjs.expr.right, sf); + for (const prop of table?.properties ?? []) { + if (prop.name === undefined || !ts.isIdentifier(prop.name) || prop.name.text !== name) + continue; + if (ts.isPropertyAssignment(prop)) values.push(prop.initializer); + else if (ts.isShorthandPropertyAssignment(prop)) { + const value = lowerer.checker.getShorthandAssignmentValueSymbol(prop); + if (!symbolIsThisFreeFunction(lowerer, value)) return false; + shorthandOnly = true; + } else if (ts.isMethodDeclaration(prop)) methodEntry = prop; + } + } + if (methodEntry !== null) return values.length === 0 && !functionUsesThis(methodEntry); + if (values.length === 0) return shorthandOnly; + return values.every((v) => { + let e = v; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) return !functionUsesThis(e); + if (ts.isIdentifier(e)) { + return symbolIsThisFreeFunction(lowerer, lowerer.checker.getSymbolAtLocation(e)); + } + // Another module's member (`exports.f = require("./lib").f`, or through + // a module binding): that module's own export values decide. + if (ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.name)) { + const dep = ts.isIdentifier(e.expression) + ? lowerer.cjsLocalModuleBindingOf(e.expression) + ? lowerer.cjsModuleBindingDepOf(e.expression) + : null + : lowerer.cjsModuleRefDepOf(e.expression); + return dep !== null && cjsExportCallIgnoresThis(lowerer, dep, e.name.text, depth + 1); + } + return false; + }); +} diff --git a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts new file mode 100644 index 000000000..7f31337f5 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts @@ -0,0 +1,370 @@ +/* Inline CommonJS `require("./x")` of a program module in expression + * position. A top-level `const m = require("./x")` binding is alias + * plumbing: member reads resolve to the exporter's declarations, and the + * statement lowers to the dependency's run-once %init call. An inline + * require is the same thing without the binding: the %init call runs at + * exactly the call's position (Node evaluates the module there, or + * answers from its cache), and the value is then read statically: + * + * - `require("./x").f` and `require("./x").f(...)` over a module whose + * exports are a table (member exports or `module.exports = {...}`) + * read the member like a binding would; + * - `require("./x")` as a value over a module whose whole export is one + * value (`module.exports = fn` / `= class {}` / `= make()` / `= 42` / + * `= require("./y")`) answers that value. + * + * A table module's exports object as a value has no static + * representation; it keeps a named refusal (a first-class CommonJS module + * object is the two-tier design's S9 stage). */ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; +import { + canonicalBuiltinModule, + cjsExportAssignmentOf, + cjsExportDiscardReason, + cjsExportTargetLiteral, + isCjsJsFile, + isJsSourceFile, + isModuleExportsAccess, + locOf, + npmStaticDepSf7, + requireSpecOf, + resolveImport, +} from "../program.js"; +import { createRequireCalleeFileOf } from "./builtins/module-bindings.js"; +import { isCjsRequireGlobal } from "./lower-node-module.js"; +import { DYN, type IrExpr } from "../../ir/ir.js"; +import { nodeThrowExpr } from "../../ir/build.js"; +import { probeNodeRequireRefusal } from "../npm.js"; + +export interface InlineRequire { + call: ts.CallExpression; + spec: string; + dep: ts.SourceFile; +} + +/** A `require("")` call of the CommonJS global in a CommonJS JS + * file whose target is a compiled program module (relative, aliased, or an + * opted-in --npm-static package). Builtins, JSON documents and + * createRequire bindings answer null: their own lowerings own them. */ +export function inlineProgramRequireOf( + lowerer: Lowerer, + expr: ts.Expression, +): InlineRequire | null { + let e = expr; + while (ts.isParenthesizedExpression(e)) e = e.expression; + 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(); + if (!isCjsJsFile(sf, lowerer.program)) return null; + if (!isCjsRequireGlobal(lowerer, e.expression)) return null; + if (createRequireCalleeFileOf(lowerer, e.expression) !== null) return null; + const dep = + resolveImport(lowerer.program, sf, spec, "require") ?? + npmStaticDepSf7(lowerer.program, sf, spec, "require"); + if (!dep || dep.isDeclarationFile || dep.fileName.endsWith(".json")) return null; + return { call: e, spec, dep }; +} + +/** The kept (last, not discarded) top-level `module.exports = ` of a + * CommonJS module whose whole export is a single value, or null when its + * exports are a table: member exports only, or a literal / const-literal / + * Proxy-over-literal table. */ +export function cjsWholeExportValueOf( + sf: ts.SourceFile, +): { assign: ts.BinaryExpression; rhs: ts.Expression } | null { + let kept: ts.BinaryExpression | null = null; + for (const stmt of sf.statements) { + const cjs = cjsExportAssignmentOf(stmt); + if (cjs?.kind === "table" && cjsExportDiscardReason(stmt) === null) kept = cjs.expr; + } + if (kept === null) return null; + let rhs: ts.Expression = kept.right; + while (ts.isParenthesizedExpression(rhs)) rhs = rhs.expression; + if (ts.isObjectLiteralExpression(rhs) || cjsExportTargetLiteral(rhs, sf) !== null) return null; + return { assign: kept, rhs }; +} + +/** True when `dep`'s exports, through `module.exports = require("./y")` + * re-export chains, are a table that member reads resolve against. */ +export function cjsNamespaceModule(lowerer: Lowerer, dep: ts.SourceFile, depth = 0): boolean { + const whole = cjsWholeExportValueOf(dep); + if (whole === null) return true; + const inner = inlineProgramRequireOf(lowerer, whole.rhs); + return inner !== null && depth < 32 && cjsNamespaceModule(lowerer, inner.dep, depth + 1); +} + +/** An inline program-module require over a table module (member reads and + * member calls resolve through it), or null. */ +export function inlineNamespaceRequireOf( + lowerer: Lowerer, + expr: ts.Expression, +): InlineRequire | null { + const req = inlineProgramRequireOf(lowerer, expr); + return req !== null && cjsNamespaceModule(lowerer, req.dep) ? req : null; +} + +/** `value` evaluated after the required module's run-once %init (and its + * module-registry link) at the require's own position. */ +export function withRequireInit(lowerer: Lowerer, req: InlineRequire, value: IrExpr): IrExpr { + if (lowerer.inlineRequireCycleCalls.has(req.call)) { + lowerer.unsupported( + "SC1090", + req.call, + `require() of '${req.spec}' while it is still evaluating (a require cycle: Node answers its partial exports, which the static export table does not model)`, + ); + } + // Every inline program require is a preflight module edge, so its module + // has an initializer; never read its exports without evaluating it. + if (!lowerer.initNameOf.has(req.dep)) { + lowerer.unsupported( + "SC1090", + req.call, + `require() of '${req.spec}' here (the module is not part of the compiled module order)`, + ); + } + const init = lowerer.requireInitStmt(req.spec, req.call); + if (init === null) return value; + return { kind: "seqExpr", stmts: [init], result: value, type: value.type, loc: value.loc }; +} + +/** The whole-export value of `dep` read from the requirer, following + * `module.exports = require("./y")` re-exports (the inner module already + * evaluated inside dep's own %init). Null for table modules and for + * whole-export shapes with no represented value. */ +export function wholeExportValue( + lowerer: Lowerer, + dep: ts.SourceFile, + blame: ts.Node, + depth = 0, +): IrExpr | null { + const whole = cjsWholeExportValueOf(dep); + if (whole === null || depth > 32) return null; + const inner = inlineProgramRequireOf(lowerer, whole.rhs); + if (inner !== null) return wholeExportValue(lowerer, inner.dep, blame, depth + 1); + // An identifier names a declaration of the exporting module (or a + // builtin global): reading it IS the export, exactly what requirer + // bindings alias to. + if (ts.isIdentifier(whole.rhs)) return lowerer.lowerExpr(whole.rhs); + if (ts.isClassExpression(whole.rhs)) { + return lowerer.classValueRef(lowerer.lowerClassExpressionInfo(whole.rhs), blame); + } + const g = lowerer.globalsByDeclNode.get(whole.assign); + if (g === undefined) return null; + return { kind: "varRef", localId: g.id, type: g.type, loc: locOf(blame) }; +} + +/** `require("./x")` as a value (`require("debug")("ns")`, `new + * (require("./C"))()`, `{ verify: require("./verify") }`): the module's + * %init at this position, then its whole-export value. Null when `call` + * is not an inline program-module require. */ +export function lowerInlineRequireValue(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const req = inlineProgramRequireOf(lowerer, call); + if (req === null) return null; + const value = wholeExportValue(lowerer, req.dep, call, 0); + if (value === null) { + lowerer.unsupported( + "SC1090", + call, + cjsNamespaceModule(lowerer, req.dep) + ? `the exports object of '${req.spec}' as a value (its exports compile to a static table: read require(${JSON.stringify(req.spec)}).member directly, or bind const m = require(${JSON.stringify(req.spec)}) at the module's top level)` + : `the whole export of '${req.spec}' as a value (its 'module.exports =' value has no static representation)`, + ); + } + return withRequireInit(lowerer, req, value); +} + +/** Lowers `inner` (a member read or member call whose receiver is the + * inline require) with that require marked as handled, so the receiver + * resolves as a module binding, then sequences the module's %init first. */ +export function lowerWithInlineRequireInit( + lowerer: Lowerer, + req: InlineRequire, + inner: () => IrExpr, +): IrExpr { + lowerer.inlineRequireInitDone.add(req.call); + try { + return withRequireInit(lowerer, req, inner()); + } finally { + lowerer.inlineRequireInitDone.delete(req.call); + } +} + +/** A `require(...)` call of the CommonJS global in a CommonJS file that no + * other lowering claimed (not a builtin, JSON document, program module or + * createRequire binding): a package nothing installed resolves throws + * Node's catchable MODULE_NOT_FOUND at the call, and every other form is + * refused by name instead of as an unlowerable `require` value. */ +export function lowerUncompiledRequireCall( + lowerer: Lowerer, + call: ts.CallExpression, +): IrExpr | null { + if (!ts.isIdentifier(call.expression) || call.expression.text !== "require") return null; + const sf = call.getSourceFile(); + if (!isCjsJsFile(sf, lowerer.program) || !isCjsRequireGlobal(lowerer, call.expression)) + return null; + if (createRequireCalleeFileOf(lowerer, call.expression) !== null) return null; + const spec = requireSpecOf(call); + if (spec === null) { + lowerer.unsupported( + "SC1090", + call, + 'require() with a computed specifier (the compiled module graph is fixed at build time: require("") lowers)', + ); + } + if (canonicalBuiltinModule(spec) !== null) return null; + if (!spec.startsWith(".") && !spec.startsWith("/") && !spec.startsWith("#")) { + const refusal = probeNodeRequireRefusal(sf.fileName, spec); + if (refusal !== null) { + return nodeThrowExpr(0, "MODULE_NOT_FOUND", refusal.message, DYN, locOf(call)); + } + lowerer.unsupported( + "SC1090", + call, + `require() of the package '${spec}' in an expression (the package is not compiled into this program)`, + ); + } + lowerer.unsupported( + "SC1090", + call, + `require() of '${spec}' in an expression (it does not resolve to a compiled program module)`, + ); +} + +/** The elements of a top-level `const { key: { ... } } = require("./x")` + * declaration over a program module whose nested pattern destructures the + * named export's value (preflight admits exactly these). Plain elements + * stay alias plumbing. */ +export function requireNestedPatternsOf(decl: ts.VariableDeclaration): ts.BindingElement[] { + 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 []; + return decl.name.elements.filter( + (e) => + e.name !== undefined && + ts.isObjectBindingPattern(e.name) && + e.propertyName !== undefined && + ts.isIdentifier(e.propertyName) && + e.initializer === undefined && + e.dotDotDotToken === undefined, + ); +} + +/** A plain method entry of a `module.exports = { ... }` table: identifier + * name, a body, not a generator. Its value is the method's function. */ +export function cjsTableMethodEntry( + prop: ts.ObjectLiteralElementLike, +): (ts.MethodDeclaration & { name: ts.Identifier; body: ts.Block }) | null { + if (!ts.isMethodDeclaration(prop) || !ts.isIdentifier(prop.name) || prop.body === undefined) + return null; + if (prop.asteriskToken !== undefined) return null; + return prop as ts.MethodDeclaration & { name: ts.Identifier; body: ts.Block }; +} + +/** `exports. = value` / `module.exports. = value` assignments in + * VALUE position inside a top-level statement (`const src = exports.src = + * []`), outside function bodies and outside the statement's own export + * chain (`statementExport`, which the statement-level machinery owns). */ +export function nestedCjsExportAssignmentsOf( + stmt: ts.Statement, + statementExport: ts.BinaryExpression | undefined, +): { name: ts.Identifier; value: ts.Expression }[] { + if (!ts.isVariableStatement(stmt) && !ts.isExpressionStatement(stmt)) return []; + const owned = new Set(); + for (let e: ts.Expression | undefined = statementExport; e !== undefined;) { + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.EqualsToken) break; + owned.add(e); + e = e.right; + } + const out: { name: ts.Identifier; value: ts.Expression }[] = []; + ts.walkPreorder(stmt, (node) => { + if ( + node !== stmt && + (ts.isFunctionLike(node) || ts.isClassDeclaration(node) || ts.isClassExpression(node)) + ) + return "skip"; + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken && + !owned.has(node) && + ts.isPropertyAccessExpression(node.left) && + node.left.questionDotToken === undefined && + ts.isIdentifier(node.left.name) && + ((ts.isIdentifier(node.left.expression) && node.left.expression.text === "exports") || + isModuleExportsAccess(node.left.expression)) + ) { + out.push({ name: node.left.name, value: node.right }); + } + return undefined; + }); + return out; +} + +/** `const { A, B: b } = ns` at a module's top level, where `ns` is a + * CommonJS module binding over a table module (picomatch's `const { + * MAX_LENGTH, ... } = constants`): each name takes a snapshot of the named + * export's value at this statement, exactly like destructuring the exports + * object would. Null for every other declaration; the plain elements and + * their key names otherwise. */ +export function cjsNamespaceDestructureOf( + lowerer: Lowerer, + decl: ts.VariableDeclaration, +): { dep: ts.SourceFile; elements: { name: ts.Identifier; key: string }[] } | null { + if (!ts.isObjectBindingPattern(decl.name) || decl.initializer === undefined) return null; + const stmt = decl.parent?.parent; + if (stmt === undefined || !ts.isVariableStatement(stmt) || !ts.isSourceFile(stmt.parent)) + return null; + if (!isJsSourceFile(stmt.parent)) return null; + let init: ts.Expression = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (!ts.isIdentifier(init) || !lowerer.cjsLocalModuleBindingOf(init)) return null; + const dep = lowerer.cjsModuleBindingDepOf(init); + if (dep === null) return null; + const elements: { name: ts.Identifier; key: string }[] = []; + for (const e of decl.name.elements) { + if (e.initializer !== undefined || e.dotDotDotToken !== undefined) return null; + if (e.name === undefined || !ts.isIdentifier(e.name)) return null; + const key = + e.propertyName === undefined + ? e.name.text + : ts.isIdentifier(e.propertyName) || ts.isStringLiteral(e.propertyName) + ? e.propertyName.text + : null; + if (key === null) return null; + elements.push({ name: e.name, key }); + } + return { dep, elements }; +} + +/** The value of `dep`'s export `key` read from another module: its export + * global, or the function/class declaration it aliases. Null when it has no + * static value. */ +export function cjsExportValueByName( + lowerer: Lowerer, + dep: ts.SourceFile, + key: string, + blame: ts.Node, +): IrExpr | null { + let sym = lowerer.cjsModuleExportSymbol(dep, key); + if (sym === undefined) return null; + if (sym.flags & ts.SymbolFlags.Alias) sym = lowerer.checker.getAliasedSymbol(sym); + const direct = lowerer.globalsBySymbol.get(sym); + if (direct) return { kind: "varRef", localId: direct.id, type: direct.type, loc: locOf(blame) }; + for (const d of lowerer.checker.declarationsOf(sym)) { + const byDecl = lowerer.globalsByDeclNode.get(d); + if (byDecl) return { kind: "varRef", localId: byDecl.id, type: byDecl.type, loc: locOf(blame) }; + // Alias plumbing: a shorthand/identifier table entry or a member + // export naming a declaration. Reading that identifier IS the value. + if (ts.isShorthandPropertyAssignment(d)) return lowerer.lowerShorthandValue(d); + if (ts.isPropertyAssignment(d) && ts.isIdentifier(d.initializer)) { + return lowerer.lowerExpr(d.initializer); + } + if ((ts.isFunctionDeclaration(d) || ts.isClassDeclaration(d)) && d.name !== undefined) { + return lowerer.lowerExpr(d.name); + } + } + return null; +} diff --git a/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts new file mode 100644 index 000000000..f7955fc23 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts @@ -0,0 +1,223 @@ +/* tsc's CommonJS import interop: `const x_1 = __importDefault(require("./m"))` + * and `const x_1 = __importStar(require("./m"))` at a module's top level, + * with the helpers tsc emits at the top of the file: + * + * var __importDefault = (this && this.__importDefault) || function (mod) { + * return (mod && mod.__esModule) ? mod : { "default": mod }; + * }; + * + * For a CommonJS dependency the helper's answer is fixed by the + * dependency's source: stamped with `__esModule` (tsc output), the binding + * IS the module's exports; otherwise `x_1.default` is the module's exports + * (and __importStar also copies every export onto x_1). Both are the + * static alias model a plain `const x_1 = require("./m")` already has, so + * the binding lowers as a require binding: the statement is the module's + * %init, `x_1.f` / `x_1.default.f` read the exports statically, and + * `x_1.default` over a single-value module is that value. The helper + * declarations themselves are dead once every call is such a binding (the + * self-probe `this.__importDefault` reads the pristine exports object, + * which never has the name), so they lower to nothing. */ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; +import { + isCjsJsFile, + isEsModuleStamp, + npmStaticDepSf7, + requireSpecOf, + resolveImport, +} from "../program.js"; +import { cjsNamespaceModule } from "./cjs-inline-require.js"; + +const HELPERS = new Set([ + "__importDefault", + "__importStar", + "__createBinding", + "__setModuleDefault", +]); + +export interface TscInteropBinding { + helper: "__importDefault" | "__importStar"; + spec: string; + dep: ts.SourceFile; + /** The dependency stamps `__esModule`: the binding IS its exports. */ + stamped: boolean; +} + +/** `var = (this && this.) || ` at a module's top + * level: the tsc helper declaration shape. */ +function tscHelperDeclName(stmt: ts.Statement): string | null { + if (!ts.isVariableStatement(stmt)) return null; + const decls = stmt.declarationList.declarations; + if (decls.length !== 1) return null; + const d = decls[0]!; + if (!ts.isIdentifier(d.name) || !HELPERS.has(d.name.text) || d.initializer === undefined) + return null; + let init: ts.Expression = d.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (!ts.isBinaryExpression(init) || init.operatorToken.kind !== ts.SyntaxKind.BarBarToken) + return null; + let probe: ts.Expression = init.left; + while (ts.isParenthesizedExpression(probe)) probe = probe.expression; + if ( + !ts.isBinaryExpression(probe) || + probe.operatorToken.kind !== ts.SyntaxKind.AmpersandAmpersandToken || + probe.left.kind !== ts.SyntaxKind.ThisKeyword || + !ts.isPropertyAccessExpression(probe.right) || + probe.right.expression.kind !== ts.SyntaxKind.ThisKeyword || + probe.right.name.text !== d.name.text + ) + return null; + // The fallback is the helper body: a function, or (newer tsc output) a + // conditional or IIFE producing one. It never runs when the helper is dead. + return d.name.text; +} + +/** True when the dependency stamps itself as an ES module translation. */ +function stampsEsModule(dep: ts.SourceFile): boolean { + return dep.statements.some((s) => { + if (!ts.isExpressionStatement(s)) return false; + if (isEsModuleStamp(s.expression)) return true; + const e = s.expression; + return ( + ts.isBinaryExpression(e) && + e.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(e.left) && + ts.isIdentifier(e.left.expression) && + e.left.expression.text === "exports" && + e.left.name.text === "__esModule" && + e.right.kind === ts.SyntaxKind.TrueKeyword + ); + }); +} + +/** The interop binding a top-level `const x = __importDefault(require(s))` / + * `__importStar(require(s))` declaration makes, or null. The helper must be + * the module's own tsc helper declaration and the dependency a compiled + * CommonJS program module. */ +export function tscInteropBindingOfDecl( + lowerer: Lowerer, + decl: ts.VariableDeclaration, +): TscInteropBinding | null { + if (!ts.isIdentifier(decl.name) || decl.initializer === undefined) return null; + const stmt = decl.parent?.parent; + if (stmt === undefined || !ts.isVariableStatement(stmt) || !ts.isSourceFile(stmt.parent)) + return null; + const sf = stmt.parent; + if (!isCjsJsFile(sf, lowerer.program)) return null; + let init: ts.Expression = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (!ts.isCallExpression(init) || init.arguments.length !== 1) return null; + const callee = init.expression; + if (!ts.isIdentifier(callee)) return null; + if (callee.text !== "__importDefault" && callee.text !== "__importStar") return null; + if (!sf.statements.some((s) => tscHelperDeclName(s) === callee.text)) return null; + const spec = requireSpecOf(init.arguments[0]!); + if (spec === null) return null; + const dep = + resolveImport(lowerer.program, sf, spec, "require") ?? + npmStaticDepSf7(lowerer.program, sf, spec, "require"); + if (!dep || dep.isDeclarationFile || !isCjsJsFile(dep, lowerer.program)) return null; + return { + helper: callee.text as TscInteropBinding["helper"], + spec, + dep, + stamped: stampsEsModule(dep), + }; +} + +/** The interop binding an identifier reads, or null. */ +export function tscInteropBindingOf( + lowerer: Lowerer, + expr: ts.Expression, +): TscInteropBinding | null { + if (!ts.isIdentifier(expr)) return null; + const sym = lowerer.checker.getSymbolAtLocation(expr); + const decl = sym ? lowerer.checker.declarationsOf(sym).find(ts.isVariableDeclaration) : undefined; + return decl ? tscInteropBindingOfDecl(lowerer, decl) : null; +} + +/** The module whose exports `expr` denotes through an interop binding: + * the binding itself when the dependency is stamped (or for __importStar, + * whose copy carries every export), and `binding.default` when it is not. + * Null for anything else. */ +export function tscInteropModuleOf( + lowerer: Lowerer, + expr: ts.Expression, +): TscInteropBinding | null { + let e = expr; + while (ts.isParenthesizedExpression(e)) e = e.expression; + const direct = tscInteropBindingOf(lowerer, e); + if (direct !== null) { + return direct.stamped || direct.helper === "__importStar" ? direct : null; + } + if (ts.isPropertyAccessExpression(e) && !e.questionDotToken && e.name.text === "default") { + const via = tscInteropBindingOf(lowerer, e.expression); + if (via !== null && !via.stamped) return via; + } + return null; +} + +/** `binding.default` over an unstamped single-value dependency: that value + * (the caller lowers the dependency's whole export). */ +export function tscInteropDefaultValueOf( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, +): TscInteropBinding | null { + if (expr.questionDotToken || expr.name.text !== "default") return null; + const via = tscInteropBindingOf(lowerer, expr.expression); + if (via === null || via.stamped) return null; + return cjsNamespaceModule(lowerer, via.dep) ? null : via; +} + +/** True for a tsc helper declaration every reference to which is a + * recognized interop binding's callee, its own self-probe, or a reference + * from another such dead helper: the statement lowers to nothing. */ +export function isDeadTscHelperDecl(lowerer: Lowerer, stmt: ts.Statement): boolean { + const name = tscHelperDeclName(stmt); + if (name === null) return false; + const sf = stmt.getSourceFile(); + const helperSpans = new Map(); + for (const s of sf.statements) { + const n = tscHelperDeclName(s); + if (n !== null) helperSpans.set(n, s); + } + const dead = new Set(); + for (let changed = true; changed;) { + changed = false; + for (const [helper, decl] of helperSpans) { + if (dead.has(helper)) continue; + let live = false; + ts.walkPreorder(sf, (node) => { + if (!ts.isIdentifier(node) || node.text !== helper) return undefined; + 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; + for (const [other, otherDecl] of helperSpans) { + if (other !== helper && dead.has(other)) { + if (at >= otherDecl.getStart(sf) && at < otherDecl.getEnd()) return undefined; + } + } + const call = parent; + const binding = call?.parent; + if ( + call !== undefined && + ts.isCallExpression(call) && + call.expression === node && + binding !== undefined && + ts.isVariableDeclaration(binding) && + tscInteropBindingOfDecl(lowerer, binding) !== null + ) + return undefined; + live = true; + return "stop"; + }); + if (!live) { + dead.add(helper); + changed = true; + } + } + } + return dead.has(name); +} diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 444a9cf23..120032bf5 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -1159,7 +1159,9 @@ export class ClassDynamicDispatch { // Runtime-owned roots keep their dedicated property contracts. A // family whose fields all exist from construction and are never // deleted keeps a fixed layout, as TypeScript classes do. - this.deletedNames ??= deletedPropertyNames(lowerer.moduleOrder); + // null is a result too; undefined means not computed yet. + if (this.deletedNames === undefined) + this.deletedNames = deletedPropertyNames(lowerer.moduleOrder); if (!root.base?.def.runtime && !fixedFieldFamily(root, this.deletedNames)) track(root); } const loc = this.propertyBag.loc; diff --git a/packages/compiler/src/frontend/lowering/construction-escapes.ts b/packages/compiler/src/frontend/lowering/construction-escapes.ts index a1626f12d..484920a1c 100644 --- a/packages/compiler/src/frontend/lowering/construction-escapes.ts +++ b/packages/compiler/src/frontend/lowering/construction-escapes.ts @@ -124,15 +124,6 @@ export function constructionEscape(inputs: ConstructionInputs): ConstructionEsca continue; } if (ts.isExpressionStatement(stmt) && !exposes(stmt.expression, depth)) continue; - // A check that only reads assigned fields and throws or does nothing - // with the receiver (`if (this.size < 0) throw new RangeError()`) - // does not expose the instance; assignments in its branches are not - // definite and still count as exposures. - if ( - (ts.isIfStatement(stmt) || ts.isThrowStatement(stmt) || ts.isBlock(stmt)) && - !exposes(stmt, depth) - ) - continue; return false; } return true; diff --git a/packages/compiler/src/frontend/lowering/containers/bytes.ts b/packages/compiler/src/frontend/lowering/containers/bytes.ts index 7a19927de..c41bf5163 100644 --- a/packages/compiler/src/frontend/lowering/containers/bytes.ts +++ b/packages/compiler/src/frontend/lowering/containers/bytes.ts @@ -1511,11 +1511,23 @@ function lowerBufferStaticValue( } else { list = lowerer.lowerExpr(argNode); if (!typeEquals(list.type, arrayOf(BYTES_U8))) { - lowerer.noLowering( - `Buffer.concat of '${lowerer.fmt(list.type)}' values`, - argNode, - "one Uint8Array[]/Buffer[] value (or literal) is the lowered list shape", - ); + // Any other list (JavaScript's untyped `const chunks = []`, or a + // union of arrays): the runtime checks it as Node does (an Array of + // Uint8Arrays, the ERR_INVALID_ARG_TYPE errors otherwise) before + // concatenating. + const value: IrExpr = + list.type.kind === "dyn" ? list : { kind: "dynFrom", value: list, type: DYN, loc }; + if (args.length === 2) { + const total = lowerer.lowerExprExpecting(args[1]!, F64); + return { + kind: "libCall", + fn: "buffer.concatLenDyn", + args: [value, total], + type: BYTES_U8, + loc, + }; + } + return { kind: "libCall", fn: "buffer.concatDyn", args: [value], type: BYTES_U8, loc }; } } if (args.length === 2) { diff --git a/packages/compiler/src/frontend/lowering/containers/for-of.ts b/packages/compiler/src/frontend/lowering/containers/for-of.ts index 4aa24b0b3..6aa838dea 100644 --- a/packages/compiler/src/frontend/lowering/containers/for-of.ts +++ b/packages/compiler/src/frontend/lowering/containers/for-of.ts @@ -747,7 +747,7 @@ function lowerForOfMapOrSet( * Recurses through every nested statement list; nested functions are * lifted elsewhere and never appear here, so every return found exits * THIS function through the active iteration. */ -function exitBeforeReturns(stmts: IrStmt[], make: () => IrStmt): IrStmt[] { +export function exitBeforeReturns(stmts: IrStmt[], make: () => IrStmt): IrStmt[] { const walk = (list: IrStmt[]): IrStmt[] => list.flatMap((s): IrStmt[] => { switch (s.kind) { diff --git a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts index f0928d2a1..7cb3753ac 100644 --- a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts +++ b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts @@ -4,7 +4,10 @@ import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; import { BOOL, + CAUGHT, F64, + type IrMapIntrinsicMethod, + type IrSetIntrinsicMethod, type IrExpr, type IrFunction, type IrLocal, @@ -16,11 +19,22 @@ import { UNDEFINED_T, VOID, arrayOf, + isUnitType, typeEquals, } from "../../../ir/ir.js"; import { locOf } from "../../program.js"; -import { typeKey } from "../../type-mapper.js"; +import { genResultRecord, typeKey } from "../../type-mapper.js"; import { concatStrings, throwTypeError } from "./dynamic-receivers.js"; +import { + type GenType, + generatorLoopBinding, + genStep, + genValue, + stepDrivable, +} from "../lower-generators.js"; +import { exitBeforeReturns } from "./for-of.js"; +import { iteratorSlotAdapter } from "../iterator-adapters.js"; +import { lowerArrayCallback } from "./callback-arguments.js"; /* ── iterator helpers (ES2025) ─────────────────────────────────────────── */ @@ -37,9 +51,9 @@ import { concatStrings, throwTypeError } from "./dynamic-receivers.js"; * raw value in the message) through an interned checker call inserted * at the argument's evaluation position. The source array is iterated * LIVE by index against a re-read length, exactly the array iterator's - * contract. Everything else — iterator objects stored in bindings, - * generator/Set/Map receivers, Iterator.from — keeps the lib fences. - * Null when the callee isn't a terminal over such a chain. */ + * contract. Chains over generators and other iterator objects lower in + * lowerGeneratorHelperCall. Null when the callee isn't a terminal over + * such a chain. */ export function lowerIteratorHelperCall( lowerer: Lowerer, call: ts.CallExpression, @@ -51,7 +65,7 @@ export function lowerIteratorHelperCall( // Walk receiver: stage* ← values()/entries() ← array-typed source // (entries seeds the chain with [index, element] pairs — the // checker's own tuple type). - const stages: { name: string; argNode: ts.Expression }[] = []; + const stages: StageNode[] = []; let cur: ts.Expression = access.expression; let source: ts.Expression | null = null; let srcProj: "values" | "entries" = "values"; @@ -73,16 +87,14 @@ export function lowerIteratorHelperCall( ts.isSpreadElement(cur.arguments[0]!) ) return null; - stages.push({ name, argNode: cur.arguments[0]! }); + stages.push({ name, argNode: cur.arguments[0]!, access: cur.expression }); cur = cur.expression.expression; } stages.reverse(); // source order for (const s of stages) { - const acc = findStageAccess(call, s); - if (acc !== null && !lowerer.isStdlibMember(acc)) return null; + if (!lowerer.isStdlibMember(s.access)) return null; } if (!lowerer.isStdlibMember(access)) return null; - const loc = locOf(call); // Lower everything in SOURCE ORDER (the chain's own evaluation // order): the source array, each stage argument, the terminal's. const items = lowerer.lowerExpr(source); @@ -90,81 +102,502 @@ export function lowerIteratorHelperCall( // The entries() seed: the interned [number, T] tuple — the same shape // the checker's own [number, T] maps to, so stage callbacks typed // against the lib's pairs intern equal. - let elem: IrType = - srcProj === "entries" - ? { - kind: "record", - shapeId: lowerer.shapes.intern( - [ - { name: "0", type: F64 }, - { name: "1", type: items.type.elem }, - ], - true, - ), - } - : items.type.elem; - type StageIr = - | { - kind: "map" | "filter" | "flatMap"; - fn: IrExpr & { type: IrType & { kind: "func" } }; - out: IrType; - } - | { kind: "take" | "drop"; budget: IrExpr }; - const stageIrs: StageIr[] = []; - for (const s of stages) { - if (s.name === "take" || s.name === "drop") { - const n = lowerer.lowerExpr(s.argNode); - if (n.type.kind !== "f64") lowerer.badType(s.argNode, lowerer.typeOf(s.argNode)); - // Node validates at the take()/drop() call — the checked budget - // rides an interned helper call AT this argument position. - stageIrs.push({ + const elem: IrType = + srcProj === "entries" ? entryPairType(lowerer, items.type.elem) : items.type.elem; + return lowerFusedChain( + lowerer, + call, + terminal, + stages, + { kind: "array", type: items.type, proj: srcProj }, + items, + elem, + ); +} + +/** Iterator-helper calls whose receiver is an iterator object: a + * generator, a stored array iterator, a helper result (IteratorObject), + * an `Iterator` value, or a Map/Set iterator (adapted into a + * generator over the live collection). + * + * - A terminal (toArray/forEach/reduce/some/every/find) fuses the chain + * of helper stages before it into one loop over the source generator, + * with the helpers' pull order and closing: the source is closed (its + * `return()` runs) when take's budget is spent, a short-circuit + * terminal hits, or a callback throws, and is not closed when it is + * exhausted or its own `next()` throws. + * - A stage (map/filter/take/drop/flatMap) outside such a chain builds + * an iterator helper object: a generator over its source that pulls + * lazily, closes the source on early exit, and closes it on a + * `return()` before the first `next()` (`closesSource`). + * + * Budgets and callbacks validate at the call, as in Node. Null when the + * call is not such a helper call. */ +export function lowerGeneratorHelperCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (call.questionDotToken || access.questionDotToken) return null; + const name = access.name.text; + const isTerminal = ITER_TERMINALS.has(name); + if (!isTerminal && !ITER_STAGES.has(name)) return null; + if (!isHelperReceiver(lowerer, access.expression)) return null; + if (!lowerer.isStdlibMember(access)) return null; + const loc = locOf(call); + if (!isTerminal) { + const argNode = call.arguments[0]; + if (call.arguments.length !== 1 || argNode === undefined || ts.isSpreadElement(argNode)) { + lowerer.noLowering(`.${name} with ${call.arguments.length} arguments`, call); + } + 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); + } + // 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); + return { kind: "seqExpr", stmts: [bound.decl], result: helper, type: helper.type, loc }; + } + // The stages before the terminal, back to the first receiver that is + // not itself a helper stage. + const stages: StageNode[] = []; + let cur: ts.Expression = access.expression; + while (isStageCall(lowerer, cur)) { + const stageAccess = cur.expression as ts.PropertyAccessExpression; + stages.push({ name: stageAccess.name.text, argNode: cur.arguments[0]!, access: stageAccess }); + cur = stageAccess.expression; + } + stages.reverse(); // source order + // A chain from m.keys()/values()/entries() (or a Set's) walks the + // collection itself, live, as for-of does. + const collection = collectionIteratorCall(lowerer, cur); + if (collection !== null) { + const items = lowerer.lowerExpr(collection.receiver); + if (items.type.kind !== "map" && items.type.kind !== "set") + lowerer.badType(collection.receiver, lowerer.typeOf(collection.receiver)); + const source: ChainSource = { kind: "collection", type: items.type, proj: collection.proj }; + return lowerFusedChain( + lowerer, + call, + name, + stages, + source, + items, + collectionSeed(lowerer, source), + ); + } + const src = lowerHelperSource(lowerer, cur); + return lowerFusedChain( + lowerer, + call, + name, + stages, + { kind: "generator", type: src.gen.type, elem: src.elem }, + src.gen, + src.elem, + ); +} + +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 + * generators for the helpers. */ +const ADAPTED_ITERATOR_TYPES = new Set(["MapIterator", "SetIterator"]); + +/** `for (x of )` where the head is a run of map/filter/take/drop + * stages over an array iterator, a Map or Set iterator, or a generator: + * one loop over the source with the stages nested in it and the loop body + * innermost, instead of a helper object per stage. The pull order and the + * closing are the helpers': `break`, `return` and throws from the body + * close a generator source (run its `return()`), and take closes it on + * the pass after its last delivery. Chains with flatMap keep the helper + * objects (a `break` there leaves an inner loop). Null when the head is + * not such a chain. */ +export function lowerForOfHelperChain( + lowerer: Lowerer, + stmt: ts.ForOfStatement, + labels: string[] | undefined, +): IrStmt | null { + if (stmt.awaitModifier) return null; + if ( + ts.isVariableDeclarationList(stmt.initializer) && + (stmt.initializer.flags & ts.NodeFlags.Using) !== 0 + ) + return null; + let head: ts.Expression = stmt.expression; + while (ts.isParenthesizedExpression(head)) head = head.expression; + const stages: StageNode[] = []; + let cur: ts.Expression = head; + while (isStageCall(lowerer, cur)) { + const stageAccess = cur.expression as ts.PropertyAccessExpression; + if (stageAccess.name.text === "flatMap") return null; + stages.push({ name: stageAccess.name.text, argNode: cur.arguments[0]!, access: stageAccess }); + cur = stageAccess.expression; + } + if (stages.length === 0) return null; + stages.reverse(); // source order + const arrayRoot = arrayIteratorCall(lowerer, cur); + const collection = arrayRoot === null ? collectionIteratorCall(lowerer, cur) : null; + if (arrayRoot === null && collection === null) { + const mapped = lowerer.mapTypeOf(lowerer.typeOf(cur)); + if (mapped?.kind !== "generator" || !stepDrivable(mapped)) return null; + } + const loc = locOf(stmt); + lowerer.scopes.push(new Map()); + try { + const frame = new HostFrame(lowerer, loc); + let items: IrExpr; + let source: ChainSource; + let elem: IrType; + if (arrayRoot !== null) { + items = lowerer.lowerExpr(arrayRoot.receiver); + if (items.type.kind !== "array") + lowerer.badType(arrayRoot.receiver, lowerer.typeOf(arrayRoot.receiver)); + source = { kind: "array", type: items.type, proj: arrayRoot.proj }; + elem = + arrayRoot.proj === "entries" ? entryPairType(lowerer, items.type.elem) : items.type.elem; + } else if (collection !== null) { + items = lowerer.lowerExpr(collection.receiver); + if (items.type.kind !== "map" && items.type.kind !== "set") + lowerer.badType(collection.receiver, lowerer.typeOf(collection.receiver)); + source = { kind: "collection", type: items.type, proj: collection.proj }; + elem = collectionSeed(lowerer, source); + } else { + const src = lowerHelperSource(lowerer, cur); + items = src.gen; + source = { kind: "generator", type: src.gen.type, elem: src.elem }; + elem = src.elem; + } + const itemsId = frame.local("items", items.type, false); + const out: IrStmt[] = [{ kind: "varDecl", localId: itemsId, init: items, loc }]; + // Stage arguments evaluate in chain order, before the first pull. + const slots: StageSlot[] = []; + for (const s of stages) { + const { stage, out: next } = lowerStage( + lowerer, + s, + elem, + loc, + source.kind === "generator" + ? (varRef(itemsId, source.type, loc) as IrExpr & { type: GenType }) + : null, + ); + const arg = "budget" in stage ? stage.budget : stage.fn; + const argId = frame.local("budget" in stage ? "n" : "f", arg.type, false); + out.push({ kind: "varDecl", localId: argId, init: arg, loc }); + slots.push(stageSlot(frame, stage, argId)); + elem = next; + } + const doneId = frame.local("done", BOOL, true); + out.push(...stagePrologue(slots, doneId, loc)); + // Each delivered value lands in a hidden local the loop binding reads. + const valueId = frame.local("hv", elem, false); + const binds = generatorLoopBinding(lowerer, stmt, varRef(valueId, elem, loc), elem); + let body = lowerer.inCtl( + "loop", + () => lowerer.lowerScopedBlock(stmt.statement), + source.kind === "collection" ? undefined : labels, + ); + if (source.kind === "collection") { + // A return from the body ends the live walk first, as for-of does. + const t = source.type; + body = exitBeforeReturns(body, () => ({ + kind: "exprStmt", + expr: + t.kind === "map" + ? { + kind: "mapIntrinsic", + method: "iterExit", + receiver: varRef(itemsId, t, loc), + args: [], + type: VOID, + loc, + } + : { + kind: "setIntrinsic", + method: "iterExit", + receiver: varRef(itemsId, t, loc), + args: [], + type: VOID, + loc, + }, + loc, + })); + } + const emit = foldStages( + lowerer, + frame, + slots, + (value) => [{ kind: "varDecl", localId: valueId, init: value, loc }, ...binds, ...body], + doneId, + loc, + ); + out.push( + ...sourceLoop( + lowerer, + frame, + itemsId, + source, + doneId, + emit, + loc, + source.kind === "collection" ? undefined : labels, + ), + ); + return { kind: "block", body: out, loc }; + } finally { + lowerer.scopes.pop(); + } +} + +/** `arr.values()` or `arr.entries()` 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 { + if (!ts.isCallExpression(expr) || !ts.isPropertyAccessExpression(expr.expression)) return null; + if (expr.questionDotToken || expr.expression.questionDotToken || expr.arguments.length !== 0) + return null; + const proj = expr.expression.name.text; + if (proj !== "values" && proj !== "entries") return null; + if (lowerer.mapTypeOf(lowerer.typeOf(expr.expression.expression))?.kind !== "array") return null; + if (!lowerer.isStdlibMember(expr.expression)) return null; + return { receiver: expr.expression.expression, proj }; +} + +/** One helper stage call in a chain, in source order. */ +interface StageNode { + name: string; + argNode: ts.Expression; + access: ts.PropertyAccessExpression; +} + +/** A lowered stage: its callback, or its validated budget. */ +type StageIr = + | { + kind: "map" | "filter" | "flatMap"; + fn: IrExpr & { type: IrType & { kind: "func" } }; + } + | { kind: "take" | "drop"; budget: IrExpr }; + +/** Where a fused chain's elements come from: a live array (by index), or + * a synchronous generator driven like for-of. */ +type ChainSource = + | { kind: "array"; type: IrType & { kind: "array" }; proj: "values" | "entries" } + | { kind: "generator"; type: GenType; elem: IrType } + | { + kind: "collection"; + type: (IrType & { kind: "map" }) | (IrType & { kind: "set" }); + proj: "keys" | "values" | "entries"; + }; + +/** `m.keys()`, `m.values()` or `m.entries()` (or a Set's) on a Map or + * Set value, as the root of a helper chain. */ +function collectionIteratorCall( + lowerer: Lowerer, + expr: ts.Expression, +): { receiver: ts.Expression; proj: "keys" | "values" | "entries" } | null { + if (!ts.isCallExpression(expr) || !ts.isPropertyAccessExpression(expr.expression)) return null; + if (expr.questionDotToken || expr.expression.questionDotToken || expr.arguments.length !== 0) + return null; + const proj = expr.expression.name.text; + if (proj !== "keys" && proj !== "values" && proj !== "entries") return null; + const kind = lowerer.mapTypeOf(lowerer.typeOf(expr.expression.expression))?.kind; + if (kind !== "map" && kind !== "set") return null; + if (!lowerer.isStdlibMember(expr.expression)) return null; + return { receiver: expr.expression.expression, proj }; +} + +/** The element a collection chain starts from: a key (a Set's element), + * a Map's value, or the interned [key, value] pair (a Set's [v, v]). */ +function collectionSeed(lowerer: Lowerer, source: ChainSource & { kind: "collection" }): IrType { + const t = source.type; + const keyT = t.kind === "map" ? t.key : t.elem; + if (source.proj === "keys" || (t.kind === "set" && source.proj === "values")) return keyT; + if (source.proj === "values") return (t as IrType & { kind: "map" }).value; + return { + kind: "record", + shapeId: lowerer.shapes.intern( + [ + { name: "0", type: keyT }, + { name: "1", type: t.kind === "map" ? t.value : keyT }, + ], + true, + ), + }; +} + +/** The interned [number, T] pair an array's entries() yields. */ +function entryPairType(lowerer: Lowerer, elem: IrType): IrType { + return { + kind: "record", + shapeId: lowerer.shapes.intern( + [ + { name: "0", type: F64 }, + { name: "1", type: elem }, + ], + true, + ), + }; +} + +/** Whether `expr` is an iterator object the helpers lower over: a + * synchronous generator-typed value, or a Map/Set iterator. */ +function isHelperReceiver(lowerer: Lowerer, expr: ts.Expression): boolean { + const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (mapped?.kind === "generator") return mapped.async !== true; + const symbol = lowerer.typeOf(expr).getSymbol(); + return ( + mapped?.kind === "dyn" && + symbol !== undefined && + ADAPTED_ITERATOR_TYPES.has(symbol.name) && + lowerer.isStdlibSymbol(symbol) + ); +} + +/** Whether `expr` is a stage call (`src.map(f)`) on a helper receiver. */ +function isStageCall(lowerer: Lowerer, expr: ts.Expression): expr is ts.CallExpression { + if (!ts.isCallExpression(expr) || !ts.isPropertyAccessExpression(expr.expression)) return false; + if (expr.questionDotToken || expr.expression.questionDotToken) return false; + if (!ITER_STAGES.has(expr.expression.name.text)) return false; + if (expr.arguments.length !== 1 || ts.isSpreadElement(expr.arguments[0]!)) return false; + return ( + isHelperReceiver(lowerer, expr.expression.expression) && lowerer.isStdlibMember(expr.expression) + ); +} + +/** The source of a helper call as a synchronous generator — the receiver + * itself, or a Map/Set iterator adapted into a generator over the live + * collection — and its element type. A generator whose yield channel is + * checked-dynamic beside a declared element type (`Generator

` boxes a + * record yield next to its `any` return channel) has each value converted + * to the declared type as it is pulled. */ +function lowerHelperSource( + lowerer: Lowerer, + expr: ts.Expression, +): { gen: IrExpr & { type: GenType }; elem: IrType } { + const tsType = lowerer.typeOf(expr); + const mapped = lowerer.mapTypeOf(tsType); + const arg = lowerer.checker.getTypeArguments(tsType as ts.TypeReference)[0]; + if (mapped?.kind === "generator") { + const gen = lowerer.lowerExpr(expr); + if (gen.type.kind !== "generator" || gen.type.async) lowerer.badType(expr, tsType); + const genT = gen.type; + const declared = genT.yieldT.kind === "dyn" && arg ? lowerer.mapTypeOf(arg) : null; + const elem = + declared !== null && + declared.kind !== "dyn" && + declared.kind !== "void" && + !isUnitType(declared) + ? declared + : genT.yieldT; + return { gen: gen as IrExpr & { type: GenType }, elem }; + } + const yieldT = arg ? lowerer.mapTypeOf(arg) : null; + if ( + !yieldT || + yieldT.kind === "void" || + !genResultRecord(yieldT, VOID, lowerer.shapes, lowerer.unions) + ) + lowerer.badType(expr, tsType); + const target: GenType = { kind: "generator", yieldT, retT: VOID, nextT: UNDEFINED_T }; + 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 }; +} + +/** Lower one stage's argument against its input element type: the + * callback (value) or (value, counter), or the take/drop budget with + * Node's eager RangeError check at the argument's position. `out` is + * the stage's output element type. */ +function lowerStage( + lowerer: Lowerer, + s: StageNode, + elem: IrType, + loc: SrcLoc, + source: (IrExpr & { type: GenType }) | null = null, +): { stage: StageIr; out: IrType } { + if (s.name === "take" || s.name === "drop") { + const n = lowerer.lowerExpr(s.argNode); + if (n.type.kind !== "f64") lowerer.badType(s.argNode, lowerer.typeOf(s.argNode)); + // Node validates at the take()/drop() call — the checked budget + // rides an interned helper call AT this argument position. A + // generator source is closed before the RangeError, as Node closes + // the iterator the helper was called on. + return { + stage: { kind: s.name, budget: { kind: "call", - callee: internBudgetChecker(lowerer, loc), - args: [n], + callee: internBudgetChecker(lowerer, loc, source?.type ?? null), + args: source === null ? [n] : [n, source], type: F64, loc, }, - }); - continue; - } - const f = lowerer.lowerExpr(s.argNode); - if ( - f.type.kind !== "func" || - f.type.params.length > 2 || - (f.type.params.length >= 1 && !typeEquals(f.type.params[0]!, elem)) || - (f.type.params.length === 2 && f.type.params[1]!.kind !== "f64") - ) { - lowerer.badType(s.argNode, lowerer.typeOf(s.argNode)); - } - const fn = f as IrExpr & { type: IrType & { kind: "func" } }; - if (s.name === "map") { - stageIrs.push({ kind: "map", fn, out: fn.type.ret }); - elem = fn.type.ret; - } else if (s.name === "filter") { - if (fn.type.ret.kind !== "bool") lowerer.badType(s.argNode, lowerer.typeOf(s.argNode)); - stageIrs.push({ kind: "filter", fn, out: elem }); - } else { - if (fn.type.ret.kind !== "array") { - lowerer.noLowering( - `.flatMap over a callback returning '${lowerer.fmt(fn.type.ret)}'`, - s.argNode, - "an array-returning callback is the supported form (iterators/strings have no lowering here)", - ); - } - stageIrs.push({ kind: "flatMap", fn, out: fn.type.ret.elem }); - elem = fn.type.ret.elem; - } + }, + out: elem, + }; } - // The terminal's own arguments. + // A flatMap callback returns its own array or generator type (an + // unannotated JavaScript arrow would otherwise return checked values). + let expectedReturn: IrType | undefined; + if (s.name === "flatMap") { + const sig = lowerer.checker.getCallSignatures(lowerer.typeOf(s.argNode))[0]; + const ret = sig ? lowerer.mapTypeOf(lowerer.checker.getReturnTypeOfSignature(sig)) : null; + if (ret?.kind === "array" || ret?.kind === "generator") expectedReturn = ret; + } + // Unannotated JavaScript parameters bind the element type. + const f = lowerArrayCallback( + lowerer, + s.argNode, + [elem], + UNDEFINED_T, + false, + expectedReturn, + ).fnArg; + if ( + f.type.kind !== "func" || + f.type.params.length > 2 || + (f.type.params.length >= 1 && !typeEquals(f.type.params[0]!, elem)) || + (f.type.params.length === 2 && f.type.params[1]!.kind !== "f64") + ) { + lowerer.badType(s.argNode, lowerer.typeOf(s.argNode)); + } + const fn = f as IrExpr & { type: IrType & { kind: "func" } }; + if (s.name === "map") return { stage: { kind: "map", fn }, out: fn.type.ret }; + if (s.name === "filter") { + if (fn.type.ret.kind !== "bool") lowerer.badType(s.argNode, lowerer.typeOf(s.argNode)); + return { stage: { kind: "filter", fn }, out: elem }; + } + const ret = fn.type.ret; + 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 }; + lowerer.noLowering( + `.flatMap over a callback returning '${lowerer.fmt(ret)}'`, + s.argNode, + "callbacks returning an array or a generator are the supported forms", + ); +} + +/** The terminal's arguments and result type over elements of `elem`. */ +function lowerTerminal( + lowerer: Lowerer, + call: ts.CallExpression, + terminal: string, + elem: IrType, +): { termArgs: IrExpr[]; resultT: IrType } { const termArgs: IrExpr[] = []; - let resultT: IrType; if (terminal === "toArray") { if (call.arguments.length !== 0) lowerer.noLowering(`.toArray with ${call.arguments.length} arguments`, call); - resultT = arrayOf(elem); - } else if ( + return { termArgs, resultT: arrayOf(elem) }; + } + if ( terminal === "forEach" || terminal === "some" || terminal === "every" || @@ -174,7 +607,7 @@ export function lowerIteratorHelperCall( if (call.arguments.length !== 1 || argNode === undefined || ts.isSpreadElement(argNode)) { lowerer.noLowering(`.${terminal} with ${call.arguments.length} arguments`, call); } - const f = lowerer.lowerExpr(argNode); + const f = lowerArrayCallback(lowerer, argNode, [elem], UNDEFINED_T).fnArg; if ( f.type.kind !== "func" || f.type.params.length > 2 || @@ -185,43 +618,84 @@ export function lowerIteratorHelperCall( lowerer.badType(argNode, lowerer.typeOf(argNode)); } termArgs.push(f); - resultT = - terminal === "forEach" ? VOID : terminal === "find" ? lowerer.withUndefinedArm(elem) : BOOL; - } else { - // reduce(f[, initial]) - const fNode = call.arguments[0]; - if ( - call.arguments.length < 1 || - call.arguments.length > 2 || - fNode === undefined || - call.arguments.some((a) => ts.isSpreadElement(a)) - ) { - lowerer.noLowering(`.reduce with ${call.arguments.length} arguments`, call); - } - const f = lowerer.lowerExpr(fNode); - if ( - f.type.kind !== "func" || - f.type.params.length < 2 || - f.type.params.length > 3 || - !typeEquals(f.type.params[1]!, elem) || - (f.type.params.length === 3 && f.type.params[2]!.kind !== "f64") || - !typeEquals(f.type.params[0]!, f.type.ret) - ) { - lowerer.badType(fNode, lowerer.typeOf(fNode)); - } - const accT = (f.type as IrType & { kind: "func" }).ret; - if (call.arguments.length === 1 && !typeEquals(accT, elem)) { - lowerer.noLowering( - ".reduce without an initial value where the accumulator's type differs from the elements'", - call, - "pass the initial value: .reduce(f, init)", - ); - } - termArgs.push(f); - if (call.arguments[1] !== undefined) - termArgs.push(lowerer.lowerExprExpecting(call.arguments[1], accT)); - resultT = accT; + return { + termArgs, + resultT: + terminal === "forEach" ? VOID : terminal === "find" ? lowerer.withUndefinedArm(elem) : BOOL, + }; } + // reduce(f[, initial]) + const fNode = call.arguments[0]; + if ( + call.arguments.length < 1 || + call.arguments.length > 2 || + fNode === undefined || + call.arguments.some((a) => ts.isSpreadElement(a)) + ) { + lowerer.noLowering(`.reduce with ${call.arguments.length} arguments`, call); + } + // The accumulator binds the checker's result type. + const accLead = lowerer.mapTypeOf(lowerer.typeOf(call)) ?? elem; + const f = lowerArrayCallback(lowerer, fNode, [accLead, elem], UNDEFINED_T).fnArg; + if ( + f.type.kind !== "func" || + f.type.params.length < 2 || + f.type.params.length > 3 || + !typeEquals(f.type.params[1]!, elem) || + (f.type.params.length === 3 && f.type.params[2]!.kind !== "f64") || + !typeEquals(f.type.params[0]!, f.type.ret) + ) { + lowerer.badType(fNode, lowerer.typeOf(fNode)); + } + const accT = (f.type as IrType & { kind: "func" }).ret; + if (call.arguments.length === 1 && !typeEquals(accT, elem)) { + lowerer.noLowering( + ".reduce without an initial value where the accumulator's type differs from the elements'", + call, + "pass the initial value: .reduce(f, init)", + ); + } + termArgs.push(f); + if (call.arguments[1] !== undefined) + termArgs.push(lowerer.lowerExprExpecting(call.arguments[1], accT)); + return { termArgs, resultT: accT }; +} + +/** A terminal over a chain of stages from `source`: one interned fused + * function, called with the source, each stage's budget or callback and + * the terminal's arguments (lowered in that, the chain's own, order). */ +function lowerFusedChain( + lowerer: Lowerer, + call: ts.CallExpression, + terminal: string, + stages: StageNode[], + source: ChainSource, + items: IrExpr, + seed: IrType, +): IrExpr { + const loc = locOf(call); + if (source.kind === "generator" && !stepDrivable(source.type)) { + lowerer.noLowering( + `.${terminal} 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", + ); + } + // A take/drop budget check closes a generator source, so the source is + // bound before the stage arguments. + const bound = + source.kind === "generator" && stages.some((s) => s.name === "take" || s.name === "drop") + ? bindSource(lowerer, items as IrExpr & { type: GenType }, loc) + : null; + if (bound !== null) items = bound.ref; + let elem = seed; + const stageIrs: StageIr[] = []; + for (const s of stages) { + const lowered = lowerStage(lowerer, s, elem, loc, bound?.ref ?? null); + stageIrs.push(lowered.stage); + elem = lowered.out; + } + const { termArgs, resultT } = lowerTerminal(lowerer, call, terminal, elem); // The checker's own result type must agree (annotation drift fences). const checkerT = lowerer.mapTypeOf(lowerer.typeOf(call)); if (terminal !== "forEach" && (checkerT === null || !typeEquals(checkerT, resultT))) { @@ -231,11 +705,14 @@ export function lowerIteratorHelperCall( "the chain's element and result types must be representable — annotate the callbacks' types", ); } - const stageKeys = stageIrs.map((s) => { - if (!s) throw new InternalCompilerError("missing iterator stage"); - return "budget" in s ? s.kind : `${s.kind}:${typeKey(s.fn.type)}`; - }); - const key = `iter:${srcProj}:${typeKey(items.type)}:${stageKeys.join(",")}:${terminal}:${termArgs.map((a) => typeKey(a.type)).join(",")}`; + const stageKeys = stageIrs.map((s) => + "budget" in s ? s.kind : `${s.kind}:${typeKey(s.fn.type)}`, + ); + const srcKey = + source.kind === "generator" + ? `${typeKey(source.type)}:${typeKey(source.elem)}` + : `${source.proj}:${typeKey(source.type)}`; + const key = `iter:${srcKey}:${stageKeys.join(",")}:${terminal}:${termArgs.map((a) => typeKey(a.type)).join(",")}`; let helper = lowerer.arrHofHelpers.get(key); if (!helper) { helper = `%iter.${terminal}.${lowerer.arrHofHelpers.size}`; @@ -244,8 +721,7 @@ export function lowerIteratorHelperCall( buildIterChainFn( lowerer, helper, - items.type, - srcProj, + source, stageIrs, terminal, termArgs.map((a) => a.type), @@ -254,35 +730,67 @@ export function lowerIteratorHelperCall( ), ); } - const budgetOrFn = stageIrs.map((s): IrExpr => { - if (!s) throw new InternalCompilerError("missing iterator stage"); - return "budget" in s ? s.budget : s.fn; - }); - return { + const budgetOrFn = stageIrs.map((s): IrExpr => ("budget" in s ? s.budget : s.fn)); + const fused: IrExpr = { kind: "call", callee: helper, args: [items, ...budgetOrFn, ...termArgs], type: resultT, loc, }; + return bound === null + ? fused + : { kind: "seqExpr", stmts: [bound.decl], result: fused, type: resultT, loc }; } -const ITER_STAGES = new Set(["map", "filter", "take", "drop", "flatMap"]); -const ITER_TERMINALS = new Set(["toArray", "forEach", "reduce", "some", "every", "find"]); - -/** The ts.PropertyAccessExpression of stage `s` inside the chain under - * `call` — for the stdlib-membership check. Walks the same spine the - * parser walked; null only on shape drift (checked defensively). */ -function findStageAccess( +/** An iterator helper object for one stage over `src`: an interned + * generator `%iter..N(src, f | n)` typed as the call's checker + * type (IteratorObject). */ +function lowerHelperObject( + lowerer: Lowerer, call: ts.CallExpression, - s: { name: string; argNode: ts.Expression }, -): ts.PropertyAccessExpression | null { - let cur: ts.Expression = (call.expression as ts.PropertyAccessExpression).expression; - while (ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) { - if (cur.arguments[0] === s.argNode) return cur.expression; - cur = cur.expression.expression; + src: IrExpr & { type: GenType }, + srcElem: IrType, + stage: StageIr, + out: IrType, + loc: SrcLoc, +): IrExpr { + const outT = lowerer.mapTypeOf(lowerer.typeOf(call)); + const resultType = + outT?.kind === "generator" + ? genResultRecord(outT.yieldT, outT.retT, lowerer.shapes, lowerer.unions) + : null; + if ( + outT?.kind !== "generator" || + outT.async === true || + !typeEquals(outT.yieldT, out) || + outT.retT.kind !== "void" || + resultType === null + ) { + lowerer.noLowering( + `.${stage.kind} at this result type`, + call, + "the helper's element type must be representable — annotate the callback's types", + ); } - return null; + if (!stepDrivable(src.type)) { + lowerer.noLowering( + `.${stage.kind} over an iterator yielding '${lowerer.fmt(src.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)}`; + 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), + ); + } + return { kind: "call", callee: name, args: [src, arg], type: outT, loc }; } /** The eager take/drop budget check, interned once per program: @@ -292,11 +800,11 @@ function findStageAccess( * Node's exact behavior: NaN and negative INTEGER budgets throw (the * message carries the RAW argument — take(-1.5) says "-1.5"), -0.5 * truncates to 0 and passes, Infinity passes through. */ -function internBudgetChecker(lowerer: Lowerer, loc: SrcLoc): string { - const key = "iter:budget"; +function internBudgetChecker(lowerer: Lowerer, loc: SrcLoc, srcT: GenType | null): string { + const key = srcT === null ? "iter:budget" : `iter:budget:close:${typeKey(srcT)}`; let name = lowerer.arrHofHelpers.get(key); if (name !== undefined) return name; - name = "%iter.budget"; + name = srcT === null ? "%iter.budget" : `%iter.budget.close.${lowerer.arrHofHelpers.size}`; lowerer.arrHofHelpers.set(key, name); const v = (): IrExpr => ({ kind: "varRef", localId: "v.0", type: F64, loc }); @@ -351,13 +859,52 @@ function internBudgetChecker(lowerer: Lowerer, loc: SrcLoc): string { }, loc, }; + // The source closes first; an error from its return() is dropped, so + // the RangeError is what propagates. + const closeSource: IrStmt[] = + srcT === null + ? [] + : [ + { + kind: "tryCatch", + tryBody: [ + { + kind: "exprStmt", + expr: { + kind: "genResume", + mode: "close", + gen: varRef("src.0", srcT, loc), + arg: null, + type: VOID, + loc, + }, + loc, + }, + ], + catchBody: [], + catchLocalId: "exc.0", + finallyBody: null, + loc, + }, + ]; lowerer.liftedFns.push({ name, - params: [{ localId: "v.0", name: "v", type: F64 }], + params: [ + { localId: "v.0", name: "v", type: F64 }, + ...(srcT === null ? [] : [{ localId: "src.0", name: "src", type: srcT }]), + ], returnType: F64, - locals: [{ id: "v.0", name: "v", type: F64, mutable: true }], + locals: [ + { id: "v.0", name: "v", type: F64, mutable: true }, + ...(srcT === null + ? [] + : [ + { id: "src.0", name: "src", type: srcT, mutable: false }, + { id: "exc.0", name: "exc", type: CAUGHT, mutable: false }, + ]), + ], body: [ - { kind: "if", cond: bad, then: [throwStmt], else_: null, loc }, + { kind: "if", cond: bad, then: [...closeSource, throwStmt], else_: null, loc }, { kind: "return", value: t(), loc }, ], loc, @@ -365,97 +912,30 @@ function internBudgetChecker(lowerer: Lowerer, loc: SrcLoc): string { return name; } -/** The fused chain body. One loop over the LIVE source (length re-read - * per pass), a `done` flag every loop condition carries (take closing - * the pipeline, short-circuit terminals), stage code nested inside in - * source order, flatMap as an inner loop over its returned array. - * Pre-loop, a zero take budget marks done immediately — Node pulls - * nothing at all through a take(0). */ -function buildIterChainFn( +/** A hidden local holding `gen`, and its declaration. */ +function bindSource( lowerer: Lowerer, - name: string, - itemsT: IrType & { kind: "array" }, - srcProj: "values" | "entries", - stages: { kind: string; fn?: IrExpr & { type: IrType & { kind: "func" } } }[], - terminal: string, - termArgTs: IrType[], - resultT: IrType, + gen: IrExpr & { type: GenType }, loc: SrcLoc, -): IrFunction { - const locals: IrLocal[] = [{ id: "items.0", name: "items", type: itemsT, mutable: true }]; - const params: IrParam[] = [{ localId: "items.0", name: "items", type: itemsT }]; - let n = 0; - const addLocal = (base: string, type: IrType, mutable: boolean): string => { - const id = `${base}.${n++}`; - locals.push({ id, name: base, type, mutable }); - return id; +): { ref: IrExpr & { type: GenType }; decl: IrStmt } { + const local = lowerer.declareHiddenLocal("%helperSource", gen.type); + return { + ref: varRef(local.id, gen.type, loc) as IrExpr & { type: GenType }, + decl: { kind: "varDecl", localId: local.id, init: gen, loc }, }; - const addParam = (base: string, type: IrType): string => { - const id = `${base}.${n++}`; - locals.push({ id, name: base, type, mutable: true }); - params.push({ localId: id, name: base, type }); - return id; - }; - const doneId = addLocal("done", BOOL, true); - const done = (): IrExpr => varRef(doneId, BOOL, loc); - const setDone: IrStmt = { kind: "assign", localId: doneId, value: boolLit(true, loc), loc }; - // Parameters in call order: per-stage budget/callback, then terminal's. - interface StageSlot { - kind: string; - paramId: string; - fnT?: (IrType & { kind: "func" }) | undefined; - cntId?: string | undefined; - elem: IrType; - } - // The entries() seed builds each pass's [index, element] pair — the - // same interned tuple the caller computed its stage types against. - const seedT: IrType = - srcProj === "entries" - ? { - kind: "record", - shapeId: lowerer.shapes.intern( - [ - { name: "0", type: F64 }, - { name: "1", type: itemsT.elem }, - ], - true, - ), - } - : itemsT.elem; - let elem: IrType = seedT; - const slots: StageSlot[] = []; - for (const s of stages) { - if (s.kind === "take" || s.kind === "drop") { - const paramId = addParam("n", F64); - const cntId = addLocal(s.kind === "take" ? "taken" : "dropped", F64, true); - slots.push({ kind: s.kind, paramId, cntId, elem }); - continue; - } - const fnT = s.fn!.type; - const paramId = addParam("f", fnT); - const cntId = fnT.params.length === 2 ? addLocal("c", F64, true) : undefined; - const out = - s.kind === "map" - ? fnT.ret - : s.kind === "flatMap" - ? (fnT.ret as IrType & { kind: "array" }).elem - : elem; - slots.push({ kind: s.kind, paramId, fnT, cntId, elem: out }); - elem = out; - } - const termIds = termArgTs.map((t, i) => addParam(i === 0 ? "tf" : "init", t)); - const termFnT = - termArgTs[0]?.kind === "func" ? (termArgTs[0] as IrType & { kind: "func" }) : null; - const termCntId = - (terminal === "reduce" && termFnT !== null && termFnT.params.length === 3) || - (terminal !== "reduce" && - terminal !== "toArray" && - termFnT !== null && - termFnT.params.length === 2) || - terminal === "reduce" - ? addLocal("k", F64, true) - : undefined; - const bump = (id: string): IrStmt => ({ +} + +/** Where chain code allocates its locals: a lifted helper function's own + * frame, or hidden locals of the function being lowered (a fused for-of). */ +interface ChainFrame { + local(base: string, type: IrType, mutable: boolean): string; + /** `id = id + 1` over a number local. */ + bump(id: string): IrStmt; +} + +/** `id = id + 1` over a number local. */ +function bumpLocal(id: string, loc: SrcLoc): IrStmt { + return { kind: "assign", localId: id, value: { @@ -467,18 +947,633 @@ function buildIterChainFn( loc, }, loc, - }); - const callWith = ( - fnT: IrType & { kind: "func" }, - fnId: string, - value: IrExpr, - cntId: string | undefined, - ): IrExpr => { - const args: IrExpr[] = []; - if (fnT.params.length >= 1) args.push(value); - if (cntId !== undefined && fnT.params.length >= 2) args.push(varRef(cntId, F64, loc)); - return { kind: "callValue", callee: varRef(fnId, fnT, loc), args, type: fnT.ret, loc }; }; +} + +/** Hidden locals of the function being lowered. */ +class HostFrame implements ChainFrame { + constructor( + private readonly lowerer: Lowerer, + private readonly loc: SrcLoc, + ) {} + local(base: string, type: IrType, mutable: boolean): string { + const local = this.lowerer.declareHiddenLocal(`%${base}`, type); + local.mutable = mutable; + return local.id; + } + bump(id: string): IrStmt { + return bumpLocal(id, this.loc); + } +} + +/** The locals and parameters of a lifted helper function under + * construction. */ +class LiftedFrame implements ChainFrame { + readonly locals: IrLocal[] = []; + readonly params: IrParam[] = []; + private n = 0; + constructor(private readonly loc: SrcLoc) {} + local(base: string, type: IrType, mutable: boolean): string { + const id = `${base}.${this.n++}`; + this.locals.push({ id, name: base, type, mutable }); + return id; + } + param(base: string, type: IrType): string { + const id = `${base}.${this.n++}`; + this.locals.push({ id, name: base, type, mutable: true }); + this.params.push({ localId: id, name: base, type }); + return id; + } + bump(id: string): IrStmt { + return bumpLocal(id, this.loc); + } +} + +/** Statements that drive the synchronous generator in local `genId` the + * way for-of does. Each pass runs `pre` (which may `break`), steps the + * generator, binds the yielded value and runs `body` over it (`continue` + * moves to the next pass). Leaving the loop while the generator is + * unfinished closes it: a `break` in `pre` or `body`, a throw from the + * body, or a `return()` at a yield inside it. Exhaustion and a throwing + * step leave it unclosed, as in Node. The generator counts as unfinished + * before the first step, so a `pre` that breaks at once (take(0)) closes + * a source it never pulled. `genT` must be stepDrivable. */ +function driveGenerator( + lowerer: Lowerer, + frame: ChainFrame, + genId: string, + genT: GenType, + elemT: IrType, + pre: IrStmt[], + body: (value: IrExpr) => IrStmt[], + loc: SrcLoc, + labels?: string[], +): IrStmt[] { + const g = (): IrExpr & { type: GenType } => + varRef(genId, genT, loc) as IrExpr & { type: GenType }; + const openId = frame.local("open", BOOL, true); + const setOpen = (value: boolean): IrStmt => ({ + kind: "assign", + localId: openId, + value: boolLit(value, loc), + loc, + }); + const vId = frame.local("gv", genT.yieldT, false); + // A checked-dynamic yield converts once into the element type. + const converted = typeEquals(elemT, genT.yieldT) + ? null + : lowerer.coerceToExpected(varRef(vId, genT.yieldT, loc), elemT); + if (converted !== null && !typeEquals(converted.type, elemT)) + throw new InternalCompilerError("lowerer bug: helper source element has no conversion"); + const eId = converted === null ? vId : frame.local("ev", elemT, false); + return [ + { kind: "varDecl", localId: openId, init: boolLit(true, loc), loc }, + { + kind: "tryCatch", + tryBody: [ + { + kind: "while", + cond: boolLit(true, loc), + body: [ + ...pre, + setOpen(false), + { + kind: "if", + cond: genStep(g(), loc), + then: [{ kind: "break", loc }], + else_: null, + loc, + }, + setOpen(true), + { kind: "varDecl", localId: vId, init: genValue(g(), loc), loc }, + ...(converted === null + ? [] + : [{ kind: "varDecl" as const, localId: eId, init: converted, loc }]), + ...body(varRef(eId, elemT, loc)), + ], + ...(labels && { labels }), + loc, + }, + ], + catchBody: null, + catchLocalId: null, + // After a throw, an error from closing the source is dropped. + dropFinallyErrors: true, + finallyBody: [ + { + kind: "if", + cond: varRef(openId, BOOL, loc), + then: [ + { + kind: "exprStmt", + expr: { kind: "genResume", mode: "close", gen: g(), arg: null, type: VOID, loc }, + loc, + }, + ], + else_: null, + 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( + frame: ChainFrame, + arrId: string, + arrT: IrType & { kind: "array" }, + stop: IrExpr | null, + body: (value: IrExpr) => IrStmt[], + loc: SrcLoc, +): IrStmt { + const jId = frame.local("j", F64, true); + const wId = frame.local("w", arrT.elem, false); + const inBounds: IrExpr = { + kind: "bin", + op: "<", + left: varRef(jId, F64, loc), + right: { + kind: "arrIntrinsic", + method: "length", + receiver: varRef(arrId, arrT, loc), + args: [], + type: F64, + loc, + }, + type: BOOL, + loc, + }; + return { + kind: "for", + init: { kind: "varDecl", localId: jId, init: numLit(0, loc), loc }, + cond: + stop === null + ? inBounds + : { + kind: "logical", + op: "&&", + left: inBounds, + right: { kind: "unary", op: "!", operand: stop, type: BOOL, loc }, + type: BOOL, + loc, + }, + update: frame.bump(jId), + body: [ + { + kind: "varDecl", + localId: wId, + init: { + kind: "arrayGet", + arr: varRef(arrId, arrT, loc), + index: varRef(jId, F64, loc), + type: arrT.elem, + loc, + }, + loc, + }, + ...body(varRef(wId, arrT.elem, loc)), + ], + loc, + }; +} + +/** The live walk over the Map or Set in local `collId` that for-of does: + * iterEnter, then each live index from iterStart below the current + * iterCount until `stop`, then iterExit on every exit (a throw exits + * before rethrowing). The body sees the projected element. */ +function walkCollection( + lowerer: Lowerer, + frame: ChainFrame, + collId: string, + source: ChainSource & { kind: "collection" }, + stop: IrExpr, + body: (value: IrExpr) => IrStmt[], + loc: SrcLoc, + labels?: string[], +): IrStmt[] { + const t = source.type; + const iId = frame.local("i", F64, true); + const iter = (method: string, args: IrExpr[], type: IrType): IrExpr => + t.kind === "map" + ? { + kind: "mapIntrinsic", + method: method as IrMapIntrinsicMethod, + receiver: varRef(collId, t, loc), + args, + type, + loc, + } + : { + kind: "setIntrinsic", + method: method as IrSetIntrinsicMethod, + receiver: varRef(collId, t, loc), + args, + type, + loc, + }; + const keyT = t.kind === "map" ? t.key : t.elem; + const keyRead = (): IrExpr => iter("iterKey", [varRef(iId, F64, loc)], keyT); + const valRead = (): IrExpr => + t.kind === "map" ? iter("iterValue", [varRef(iId, F64, loc)], t.value) : keyRead(); + const seedT = collectionSeed(lowerer, source); + const seed: IrExpr = + source.proj === "entries" + ? { + kind: "recordLit", + fields: [ + { name: "0", value: keyRead() }, + { name: "1", value: valRead() }, + ], + type: seedT, + loc, + } + : source.proj === "values" && t.kind === "map" + ? valRead() + : keyRead(); + const vId = frame.local("v", seedT, false); + const exit = (): IrStmt => ({ kind: "exprStmt", expr: iter("iterExit", [], VOID), loc }); + const caughtId = frame.local("exc", CAUGHT, false); + const loop: IrStmt = { + kind: "for", + init: { kind: "varDecl", localId: iId, init: iter("iterStart", [], F64), loc }, + cond: { + kind: "logical", + op: "&&", + left: { + kind: "bin", + op: "<", + left: varRef(iId, F64, loc), + right: iter("iterCount", [], F64), + type: BOOL, + loc, + }, + right: { kind: "unary", op: "!", operand: stop, type: BOOL, loc }, + type: BOOL, + loc, + }, + update: frame.bump(iId), + body: [ + { + kind: "if", + cond: iter("iterLive", [varRef(iId, F64, loc)], BOOL), + then: [ + { kind: "varDecl", localId: vId, init: seed, loc }, + ...body(varRef(vId, seedT, loc)), + ], + else_: null, + loc, + }, + ], + ...(labels && { labels }), + loc, + }; + return [ + { kind: "exprStmt", expr: iter("iterEnter", [], VOID), loc }, + { + kind: "tryCatch", + tryBody: [loop], + catchBody: [exit(), { kind: "rethrow", localId: caughtId, loc }], + catchLocalId: caughtId, + finallyBody: null, + loc, + }, + exit(), + ]; +} + +/** 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. */ +function walkInner( + lowerer: Lowerer, + frame: ChainFrame, + innerId: string, + innerT: IrType, + stop: IrExpr | null, + body: (value: IrExpr) => IrStmt[], + loc: SrcLoc, +): IrStmt[] { + if (innerT.kind === "array") return [walkArray(frame, innerId, innerT, stop, body, loc)]; + if (innerT.kind !== "generator") + throw new InternalCompilerError("lowerer bug: flatMap over a non-iterable result"); + const pre: IrStmt[] = + stop === null + ? [] + : [{ kind: "if", cond: stop, then: [{ kind: "break", loc }], else_: null, loc }]; + return driveGenerator(lowerer, frame, innerId, innerT, innerT.yieldT, pre, body, loc); +} + +/** A stage inside fused chain code: the local holding its budget or + * callback, and its counter when the callback reads one (take/drop count + * in theirs). */ +interface StageSlot { + kind: string; + argId: string; + fnT?: (IrType & { kind: "func" }) | undefined; + cntId?: string | undefined; +} + +/** A stage callback call with (value) or (value, counter), as declared. */ +function callStage( + fnT: IrType & { kind: "func" }, + fnId: string, + value: IrExpr, + cntId: string | undefined, + loc: SrcLoc, +): IrExpr { + const args: IrExpr[] = []; + if (fnT.params.length >= 1) args.push(value); + if (cntId !== undefined && fnT.params.length >= 2) args.push(varRef(cntId, F64, loc)); + return { kind: "callValue", callee: varRef(fnId, fnT, loc), args, type: fnT.ret, loc }; +} + +/** The slot for `stage` whose budget or callback is in local `argId`. */ +function stageSlot(frame: ChainFrame, stage: StageIr, argId: string): StageSlot { + if ("budget" in stage) { + return { + kind: stage.kind, + argId, + cntId: frame.local(stage.kind === "take" ? "taken" : "dropped", F64, true), + }; + } + const fnT = stage.fn.type; + return { + kind: stage.kind, + argId, + fnT, + cntId: fnT.params.length === 2 ? frame.local("c", F64, true) : undefined, + }; +} + +/** The declarations fused chain code starts with: the `done` flag — + * already true when a take budget is zero, so nothing pulls — and each + * stage counter at zero. */ +function stagePrologue(slots: StageSlot[], doneId: string, loc: SrcLoc): IrStmt[] { + let doneInit: IrExpr = boolLit(false, loc); + for (const s of slots) { + if (s.kind !== "take") continue; + const isZero: IrExpr = { + kind: "bin", + op: "===", + left: varRef(s.argId, F64, loc), + right: numLit(0, loc), + type: BOOL, + loc, + }; + doneInit = + doneInit.kind === "boolLit" + ? isZero + : { kind: "logical", op: "||", left: doneInit, right: isZero, type: BOOL, loc }; + } + const out: IrStmt[] = [{ kind: "varDecl", localId: doneId, init: doneInit, loc }]; + for (const s of slots) { + if (s.cntId !== undefined) + out.push({ kind: "varDecl", localId: s.cntId, init: numLit(0, loc), loc }); + } + return out; +} + +/** The per-element code of a chain: each stage nested in source order + * around `last` (the terminal's statements, or a for-of body), folded + * from the end outward. filter and drop skip with `continue`; take spends + * its budget before delivery and sets `done`, so the nth element flows on + * and nothing pulls after it (every pass tests done); flatMap walks its + * callback's array or generator in an inner loop that also tests done. */ +function foldStages( + lowerer: Lowerer, + frame: ChainFrame, + slots: StageSlot[], + last: (value: IrExpr) => IrStmt[], + doneId: string, + loc: SrcLoc, +): (value: IrExpr) => IrStmt[] { + const done = (): IrExpr => varRef(doneId, BOOL, loc); + const setDone: IrStmt = { kind: "assign", localId: doneId, value: boolLit(true, loc), loc }; + let emit = last; + for (let si = slots.length - 1; si >= 0; si--) { + const s = slots[si]!; + const inner = emit; + if (s.kind === "map") { + emit = (value) => { + const vId = frame.local("m", s.fnT!.ret, false); + return [ + { + kind: "varDecl", + localId: vId, + init: callStage(s.fnT!, s.argId, value, s.cntId, loc), + loc, + }, + ...(s.cntId !== undefined ? [frame.bump(s.cntId)] : []), + ...inner(varRef(vId, s.fnT!.ret, loc)), + ]; + }; + } else if (s.kind === "filter") { + emit = (value) => { + const okId = frame.local("ok", BOOL, false); + return [ + { + kind: "varDecl", + localId: okId, + init: callStage(s.fnT!, s.argId, value, s.cntId, loc), + loc, + }, + ...(s.cntId !== undefined ? [frame.bump(s.cntId)] : []), + { + kind: "if", + cond: { kind: "unary", op: "!", operand: varRef(okId, BOOL, loc), type: BOOL, loc }, + then: [{ kind: "continue", loc }], + else_: null, + loc, + }, + ...inner(value), + ]; + }; + } else if (s.kind === "drop") { + emit = (value) => [ + { + kind: "if", + cond: { + kind: "bin", + op: "<", + left: varRef(s.cntId!, F64, loc), + right: varRef(s.argId, F64, loc), + type: BOOL, + loc, + }, + then: [frame.bump(s.cntId!), { kind: "continue", loc }], + else_: null, + loc, + }, + ...inner(value), + ]; + } else if (s.kind === "take") { + emit = (value) => [ + frame.bump(s.cntId!), + { + kind: "if", + cond: { + kind: "bin", + op: ">=", + left: varRef(s.cntId!, F64, loc), + right: varRef(s.argId, F64, loc), + type: BOOL, + loc, + }, + then: [setDone], + else_: null, + loc, + }, + ...inner(value), + ]; + } else { + emit = (value) => { + const innerT = s.fnT!.ret; + const innerId = frame.local("fm", innerT, false); + return [ + { + kind: "varDecl", + localId: innerId, + init: callStage(s.fnT!, s.argId, value, s.cntId, loc), + loc, + }, + ...(s.cntId !== undefined ? [frame.bump(s.cntId)] : []), + ...walkInner(lowerer, frame, innerId, innerT, done(), inner, loc), + ]; + }; + } + } + return emit; +} + +/** The loop over a chain's source in local `itemsId`, running `emit` per + * element until `done`: a live array (length re-read per pass), a Map or + * Set walked live, or a generator driven like for-of (closed when the + * loop leaves it unfinished). `labels` name the loop for a fused for-of + * body's labeled jumps. */ +function sourceLoop( + lowerer: Lowerer, + frame: ChainFrame, + itemsId: string, + source: ChainSource, + doneId: string, + emit: (value: IrExpr) => IrStmt[], + loc: SrcLoc, + labels?: string[], +): IrStmt[] { + const done = (): IrExpr => varRef(doneId, BOOL, loc); + if (source.kind === "collection") + return walkCollection(lowerer, frame, itemsId, source, done(), emit, loc, labels); + if (source.kind === "generator") { + return driveGenerator( + lowerer, + frame, + itemsId, + source.type, + source.elem, + [{ kind: "if", cond: done(), then: [{ kind: "break", loc }], else_: null, loc }], + emit, + loc, + labels, + ); + } + const itemsT = source.type; + const seedT = source.proj === "entries" ? entryPairType(lowerer, itemsT.elem) : itemsT.elem; + const iId = frame.local("i", F64, true); + const vId = frame.local("v", seedT, false); + const elemRead = (): IrExpr => ({ + kind: "arrayGet", + arr: varRef(itemsId, itemsT, loc), + index: varRef(iId, F64, loc), + type: itemsT.elem, + loc, + }); + const seedInit: IrExpr = + source.proj === "entries" + ? { + kind: "recordLit", + fields: [ + { name: "0", value: varRef(iId, F64, loc) }, + { name: "1", value: elemRead() }, + ], + type: seedT, + loc, + } + : elemRead(); + return [ + { + kind: "for", + init: { kind: "varDecl", localId: iId, init: numLit(0, loc), loc }, + cond: { + kind: "logical", + op: "&&", + left: { + kind: "bin", + op: "<", + left: varRef(iId, F64, loc), + right: { + kind: "arrIntrinsic", + method: "length", + receiver: varRef(itemsId, itemsT, loc), + args: [], + type: F64, + loc, + }, + type: BOOL, + loc, + }, + right: { kind: "unary", op: "!", operand: done(), type: BOOL, loc }, + type: BOOL, + loc, + }, + update: frame.bump(iId), + body: [ + { kind: "varDecl", localId: vId, init: seedInit, loc }, + ...emit(varRef(vId, seedT, loc)), + ], + ...(labels && { labels }), + loc, + }, + ]; +} + +/** The fused chain function: the source, each stage's budget or callback + * and the terminal's arguments as parameters; one loop over the source + * with the stages nested in it and the terminal's statements innermost. + * A zero take budget marks done before the first pass: Node pulls nothing + * through a take(0), and closes a generator source. */ +function buildIterChainFn( + lowerer: Lowerer, + name: string, + source: ChainSource, + stages: StageIr[], + terminal: string, + termArgTs: IrType[], + resultT: IrType, + loc: SrcLoc, +): IrFunction { + const frame = new LiftedFrame(loc); + const itemsId = frame.param("items", source.type); + const doneId = frame.local("done", BOOL, true); + const setDone: IrStmt = { kind: "assign", localId: doneId, value: boolLit(true, loc), loc }; + // Parameters in call order: per-stage budget/callback, then terminal's. + const slots = stages.map((s) => + stageSlot(frame, s, frame.param("budget" in s ? "n" : "f", "budget" in s ? F64 : s.fn.type)), + ); + const termIds = termArgTs.map((t, i) => frame.param(i === 0 ? "tf" : "init", t)); + const termFnT = + termArgTs[0]?.kind === "func" ? (termArgTs[0] as IrType & { kind: "func" }) : null; + const termCntId = + (terminal === "reduce" && termFnT !== null && termFnT.params.length === 3) || + (terminal !== "reduce" && + terminal !== "toArray" && + termFnT !== null && + termFnT.params.length === 2) || + terminal === "reduce" + ? frame.local("k", F64, true) + : undefined; // Terminal state. const preLoop: IrStmt[] = []; const postLoop: IrStmt[] = []; @@ -487,7 +1582,7 @@ function buildIterChainFn( let hasAccId = ""; let resId = ""; if (terminal === "toArray") { - outId = addLocal("out", resultT, false); + outId = frame.local("out", resultT, false); preLoop.push({ kind: "varDecl", localId: outId, @@ -496,7 +1591,7 @@ function buildIterChainFn( }); postLoop.push({ kind: "return", value: varRef(outId, resultT, loc), loc }); } else if (terminal === "some" || terminal === "every") { - resId = addLocal("res", BOOL, true); + resId = frame.local("res", BOOL, true); preLoop.push({ kind: "varDecl", localId: resId, @@ -505,14 +1600,14 @@ function buildIterChainFn( }); postLoop.push({ kind: "return", value: varRef(resId, BOOL, loc), loc }); } else if (terminal === "find") { - resId = addLocal("res", resultT, true); + resId = frame.local("res", resultT, true); const undef = lowerer.wrappedUndefined(resultT, loc); if (undef === null) throw new InternalCompilerError("lowerer bug: find result union lacks undefined"); preLoop.push({ kind: "varDecl", localId: resId, init: undef, loc }); postLoop.push({ kind: "return", value: varRef(resId, resultT, loc), loc }); } else if (terminal === "reduce") { - accId = addLocal("acc", resultT, true); + accId = frame.local("acc", resultT, true); if (termIds.length === 2) { preLoop.push({ kind: "varDecl", @@ -521,7 +1616,7 @@ function buildIterChainFn( loc, }); } else { - hasAccId = addLocal("hasAcc", BOOL, true); + hasAccId = frame.local("hasAcc", BOOL, true); preLoop.push({ kind: "varDecl", localId: hasAccId, init: boolLit(false, loc), loc }); postLoop.push({ kind: "if", @@ -547,28 +1642,7 @@ function buildIterChainFn( } if (termCntId !== undefined) preLoop.push({ kind: "varDecl", localId: termCntId, init: numLit(0, loc), loc }); - // done starts true when any take budget is zero (nothing pulls). - let doneInit: IrExpr = boolLit(false, loc); - for (const s of slots) { - if (s.kind !== "take") continue; - const isZero: IrExpr = { - kind: "bin", - op: "===", - left: varRef(s.paramId, F64, loc), - right: numLit(0, loc), - type: BOOL, - loc, - }; - doneInit = - doneInit.kind === "boolLit" - ? isZero - : { kind: "logical", op: "||", left: doneInit, right: isZero, type: BOOL, loc }; - } - preLoop.unshift({ kind: "varDecl", localId: doneId, init: doneInit, loc }); - for (const s of slots) { - if (s.cntId !== undefined) - preLoop.push({ kind: "varDecl", localId: s.cntId, init: numLit(0, loc), loc }); - } + preLoop.unshift(...stagePrologue(slots, doneId, loc)); // The terminal's per-element statements over `value`. const terminalBody = (value: IrExpr): IrStmt[] => { const out: IrStmt[] = []; @@ -586,9 +1660,13 @@ function buildIterChainFn( loc, }); } else if (terminal === "forEach") { - out.push({ kind: "exprStmt", expr: callWith(termFnT!, termIds[0]!, value, termCntId), loc }); + out.push({ + kind: "exprStmt", + expr: callStage(termFnT!, termIds[0]!, value, termCntId, loc), + loc, + }); } else if (terminal === "some" || terminal === "every") { - const hit = callWith(termFnT!, termIds[0]!, value, termCntId); + const hit = callStage(termFnT!, termIds[0]!, value, termCntId, loc); const cond: IrExpr = terminal === "some" ? hit : { kind: "unary", op: "!", operand: hit, type: BOOL, loc }; out.push({ @@ -610,7 +1688,7 @@ function buildIterChainFn( ); out.push({ kind: "if", - cond: callWith(termFnT!, termIds[0]!, value, termCntId), + cond: callStage(termFnT!, termIds[0]!, value, termCntId, loc), then: [ { kind: "assign", @@ -658,230 +1736,203 @@ function buildIterChainFn( out.push({ kind: "assign", localId: accId, value: reducer(), loc }); } } - if (termCntId !== undefined) out.push(bump(termCntId)); + if (termCntId !== undefined) out.push(frame.bump(termCntId)); return out; }; - // Fold stages from the terminal outward. - let emit: (value: IrExpr) => IrStmt[] = terminalBody; - for (let si = slots.length - 1; si >= 0; si--) { - const s = slots[si]!; - const inner = emit; - if (s.kind === "map") { - emit = (value) => { - const vId = addLocal("m", s.fnT!.ret, false); - return [ - { kind: "varDecl", localId: vId, init: callWith(s.fnT!, s.paramId, value, s.cntId), loc }, - ...(s.cntId !== undefined ? [bump(s.cntId)] : []), - ...inner(varRef(vId, s.fnT!.ret, loc)), - ]; - }; - } else if (s.kind === "filter") { - emit = (value) => { - const okId = addLocal("ok", BOOL, false); - return [ - { - kind: "varDecl", - localId: okId, - init: callWith(s.fnT!, s.paramId, value, s.cntId), - loc, - }, - ...(s.cntId !== undefined ? [bump(s.cntId)] : []), - { - kind: "if", - cond: { kind: "unary", op: "!", operand: varRef(okId, BOOL, loc), type: BOOL, loc }, - then: [{ kind: "continue", loc }], - else_: null, - loc, - }, - ...inner(value), - ]; - }; - } else if (s.kind === "drop") { - emit = (value) => [ - { - kind: "if", - cond: { - kind: "bin", - op: "<", - left: varRef(s.cntId!, F64, loc), - right: varRef(s.paramId, F64, loc), - type: BOOL, - loc, - }, - then: [bump(s.cntId!), { kind: "continue", loc }], - else_: null, - loc, - }, - ...inner(value), - ]; - } else if (s.kind === "take") { - // Budget consumed BEFORE delivery: the nth element flows on with - // done already set, so no later element pulls upstream — and a - // downstream `continue` can't skip the close (the loop conditions - // carry !done). - emit = (value) => [ - bump(s.cntId!), - { - kind: "if", - cond: { - kind: "bin", - op: ">=", - left: varRef(s.cntId!, F64, loc), - right: varRef(s.paramId, F64, loc), - type: BOOL, - loc, - }, - then: [setDone], - else_: null, - loc, - }, - ...inner(value), - ]; - } else { - // flatMap: an inner loop over the returned array, `!done` carried. - emit = (value) => { - const innerT = s.fnT!.ret as IrType & { kind: "array" }; - const arrId = addLocal("fm", innerT, false); - const jId = addLocal("j", F64, true); - const wId = addLocal("w", innerT.elem, false); - return [ - { - kind: "varDecl", - localId: arrId, - init: callWith(s.fnT!, s.paramId, value, s.cntId), - loc, - }, - ...(s.cntId !== undefined ? [bump(s.cntId)] : []), - { - kind: "for", - init: { kind: "varDecl", localId: jId, init: numLit(0, loc), loc }, - cond: { - kind: "logical", - op: "&&", - left: { - kind: "bin", - op: "<", - left: varRef(jId, F64, loc), - right: { - kind: "arrIntrinsic", - method: "length", - receiver: varRef(arrId, innerT, loc), - args: [], - type: F64, - loc, - }, - type: BOOL, - loc, - }, - right: { kind: "unary", op: "!", operand: done(), type: BOOL, loc }, - type: BOOL, - loc, - }, - update: { - kind: "assign", - localId: jId, - value: { - kind: "bin", - op: "+", - left: varRef(jId, F64, loc), - right: numLit(1, loc), - type: F64, - loc, - }, - loc, - }, - body: [ - { - kind: "varDecl", - localId: wId, - init: { - kind: "arrayGet", - arr: varRef(arrId, innerT, loc), - index: varRef(jId, F64, loc), - type: innerT.elem, - loc, - }, - loc, - }, - ...inner(varRef(wId, innerT.elem, loc)), - ], - loc, - }, - ]; - }; - } - } - const iId = addLocal("i", F64, true); - const vId = addLocal("v", seedT, false); - const elemRead = (): IrExpr => ({ - kind: "arrayGet", - arr: varRef("items.0", itemsT, loc), - index: varRef(iId, F64, loc), - type: itemsT.elem, - loc, - }); - const seedInit: IrExpr = - srcProj === "entries" - ? { - kind: "recordLit", - fields: [ - { name: "0", value: varRef(iId, F64, loc) }, - { name: "1", value: elemRead() }, - ], - type: seedT, - loc, - } - : elemRead(); - const loop: IrStmt = { - kind: "for", - init: { kind: "varDecl", localId: iId, init: numLit(0, loc), loc }, - cond: { - kind: "logical", - op: "&&", - left: { - kind: "bin", - op: "<", - left: varRef(iId, F64, loc), - right: { - kind: "arrIntrinsic", - method: "length", - receiver: varRef("items.0", itemsT, loc), - args: [], - type: F64, - loc, - }, - type: BOOL, - loc, - }, - right: { kind: "unary", op: "!", operand: done(), type: BOOL, loc }, - type: BOOL, - loc, - }, - update: { - kind: "assign", - localId: iId, - value: { - kind: "bin", - op: "+", - left: varRef(iId, F64, loc), - right: numLit(1, loc), - type: F64, - loc, - }, - loc, - }, + const emit = foldStages(lowerer, frame, slots, terminalBody, doneId, loc); + return { + name, + params: frame.params, + returnType: resultT, + locals: frame.locals, body: [ - { kind: "varDecl", localId: vId, init: seedInit, loc }, - ...emit(varRef(vId, seedT, loc)), + ...preLoop, + ...sourceLoop(lowerer, frame, itemsId, source, doneId, emit, loc), + ...postLoop, ], loc, }; +} + +/** An iterator helper object's generator for one stage over `src`: + * + * map: for (v of src) yield f(v, c++) + * filter: for (v of src) if (f(v, c++)) yield v + * take: for (;;) { if (rem === 0) { close src; return } rem--; pull; yield v } + * drop: for (v of src) { if (dropped < n) { dropped++; continue } yield v } + * flatMap: for (v of src) for (w of f(v, c++)) yield w + * + * The source drive closes `src` on every early exit, including a + * `return()` at one of the yields; `closesSource` closes it on a + * `return()` before the first `next()`. */ +function buildHelperGenFn( + lowerer: Lowerer, + name: string, + srcT: GenType, + srcElem: IrType, + stage: StageIr, + argT: IrType, + outT: GenType, + resultType: IrType & { kind: "record" }, + loc: SrcLoc, +): IrFunction { + const frame = new LiftedFrame(loc); + const srcId = frame.param("src", srcT); + const argId = frame.param(stage.kind === "take" || stage.kind === "drop" ? "n" : "f", argT); + const yieldOf = (value: IrExpr): IrStmt => ({ + kind: "exprStmt", + expr: { + kind: "yieldExpr", + value, + type: outT.nextT.kind === "undefinedT" ? VOID : outT.nextT, + loc, + }, + loc, + }); + const fnT = argT.kind === "func" ? argT : null; + const counted = fnT !== null && fnT.params.length === 2; + const cntId = counted ? frame.local("c", F64, true) : null; + const callF = (value: IrExpr): IrExpr => { + const args: IrExpr[] = []; + if (fnT!.params.length >= 1) args.push(value); + if (cntId !== null) args.push(varRef(cntId, F64, loc)); + return { kind: "callValue", callee: varRef(argId, fnT!, loc), args, type: fnT!.ret, loc }; + }; + const body: IrStmt[] = []; + if (cntId !== null) body.push({ kind: "varDecl", localId: cntId, init: numLit(0, loc), loc }); + const bumpCount = (): IrStmt[] => (cntId !== null ? [frame.bump(cntId)] : []); + const n = (): IrExpr => varRef(argId, F64, loc); + if (stage.kind === "map") { + body.push( + ...driveGenerator( + lowerer, + frame, + srcId, + srcT, + srcElem, + [], + (value) => { + const mId = frame.local("m", fnT!.ret, false); + return [ + { kind: "varDecl", localId: mId, init: callF(value), loc }, + ...bumpCount(), + yieldOf(varRef(mId, fnT!.ret, loc)), + ]; + }, + loc, + ), + ); + } else if (stage.kind === "filter") { + body.push( + ...driveGenerator( + lowerer, + frame, + srcId, + srcT, + srcElem, + [], + (value) => { + const okId = frame.local("ok", BOOL, false); + return [ + { kind: "varDecl", localId: okId, init: callF(value), loc }, + ...bumpCount(), + { kind: "if", cond: varRef(okId, BOOL, loc), then: [yieldOf(value)], else_: null, loc }, + ]; + }, + loc, + ), + ); + } else if (stage.kind === "take") { + // The budget is spent before each pull; a spent budget closes the + // source on the next pass instead of pulling. + const pre: IrStmt[] = [ + { + kind: "if", + cond: { kind: "bin", op: "===", left: n(), right: numLit(0, loc), type: BOOL, loc }, + then: [{ kind: "break", loc }], + else_: null, + loc, + }, + { + kind: "assign", + localId: argId, + value: { kind: "bin", op: "-", left: n(), right: numLit(1, loc), type: F64, loc }, + loc, + }, + ]; + body.push( + ...driveGenerator( + lowerer, + frame, + srcId, + srcT, + srcElem, + 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, + [], + (value) => [ + { + kind: "if", + cond: { + kind: "bin", + op: "<", + left: varRef(droppedId, F64, loc), + right: n(), + type: BOOL, + loc, + }, + then: [frame.bump(droppedId), { kind: "continue", loc }], + else_: null, + loc, + }, + yieldOf(value), + ], + loc, + ), + ); + } else { + body.push( + ...driveGenerator( + lowerer, + frame, + srcId, + srcT, + srcElem, + [], + (value) => { + const innerT = fnT!.ret; + const innerId = frame.local("fm", innerT, false); + return [ + { kind: "varDecl", localId: innerId, init: callF(value), loc }, + ...bumpCount(), + ...walkInner(lowerer, frame, innerId, innerT, null, (w) => [yieldOf(w)], loc), + ]; + }, + loc, + ), + ); + } + body.push({ kind: "return", value: null, loc }); return { name, - params, - returnType: resultT, - locals, - body: [...preLoop, loop, ...postLoop], + params: frame.params, + returnType: VOID, + locals: frame.locals, + body, + generator: { yieldT: outT.yieldT, nextT: outT.nextT, resultType, closesSource: true }, loc, }; } diff --git a/packages/compiler/src/frontend/lowering/error-methods.ts b/packages/compiler/src/frontend/lowering/error-methods.ts index bcbbe13a4..979cf4814 100644 --- a/packages/compiler/src/frontend/lowering/error-methods.ts +++ b/packages/compiler/src/frontend/lowering/error-methods.ts @@ -315,7 +315,13 @@ export function refreshErrorPropertyDispatch( info.def.name, member, value, - [varRef("value", STRING, loc)], + [ + // A JavaScript setter takes its value untyped. + lowerer.coerceToExpected( + varRef("value", STRING, loc), + accessor.sig.params[0]?.type ?? STRING, + ), + ], VOID, loc, ), diff --git a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts index 7da847062..ceedc3e94 100644 --- a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts +++ b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts @@ -26,7 +26,8 @@ import type { Lowerer } from "../lowerer.js"; * deeper — `x?.trim().toLowerCase()` reads nothing past the guard when x * is nullish, so the whole tail must lower inside it. Walks * property/element accesses and call steps only (parens and `!` break - * the chain per the grammar: `(a?.b).c` throws on undefined in JS); + * the chain per the grammar: `(a?.b).c` throws on undefined in JS, while + * an unparenthesized `a?.b!.c` stays one chain, as TypeScript emits it); * the node's OWN token — and a call's immediate callee token — are the * plain entries' cases, not tails, and handled markers make the chain * lowering's re-dispatch walk past its own step. `f?.()` steps stay out @@ -41,6 +42,10 @@ function chainTailDot( // An active chain's bound receiver read is a LEAF — its own token // was consumed by the chain that bound it. if (lowerer.chainRecvByNode.has(cur)) return null; + if (ts.isNonNullExpression(cur)) { + cur = cur.expression; + continue; + } if (ts.isCallExpression(cur)) { // `f?.()` steps route through their own entry (or are being // handled); a deeper token under one is that chain's business. @@ -91,20 +96,29 @@ export function isOptionalChainTail(lowerer: Lowerer, expr: ts.Expression): bool if (!tail) return false; const recvT = lowerer.mapTypeOf(lowerer.typeOf(tail.expression)); if (recvT?.kind === "dyn") return true; - if (recvT?.kind !== "union") return false; - const def = lowerer.unions.get(recvT.unionId); - return !!def && def.arms.some(isUnitType); + if (recvT?.kind === "union") { + const def = lowerer.unions.get(recvT.unionId); + if (def?.arms.some(isUnitType)) return true; + } + // The checker calls the receiver present, but an unchecked read (`a[0]` + // past the end, a missing index-signature key) can still lower to an + // undefined-armed value, and `a[0]?.b.c` must then short-circuit `.c` + // too. The chain lowering inspects the lowered receiver: a present value + // re-dispatches the plain lowering, so only genuinely optional receivers + // change shape. + return lowerer.isRuntimeOptionalOrigin(tail.expression); } /** True when an EARLIER step of this access chain carries `?.` — JS * short-circuits the whole tail (`a?.b.c` reads nothing when a is * nullish), so a dyn tail read must answer undefined instead of * throwing. Walks the receiver spine only; the argument of the access - * itself is not part of the guard. */ + * itself is not part of the guard. Parentheses end the chain + * (`(a?.b).c` reads `.c` from undefined); `!` does not. */ export function hasOptionalChainGuard(expr: ts.Expression): boolean { let cur: ts.Expression = expr; for (;;) { - if (ts.isParenthesizedExpression(cur) || ts.isNonNullExpression(cur)) { + if (ts.isNonNullExpression(cur)) { cur = cur.expression; continue; } @@ -393,7 +407,8 @@ export function lowerOptionalChain( for (let cur: ts.Expression = expr; cur !== dotNode;) { const next: ts.Expression = ts.isCallExpression(cur) ? cur.expression - : (cur as ts.PropertyAccessExpression | ts.ElementAccessExpression).expression; + : (cur as ts.PropertyAccessExpression | ts.ElementAccessExpression | ts.NonNullExpression) + .expression; if (next === dotNode) break; if (!lowerer.chainNarrowedType.has(next)) { lowerer.chainNarrowedType.set( diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index 4f519691f..a3cf5c0ce 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -436,11 +436,14 @@ export function resolveInferredReturn( if (ice.length > 0) lowerer.diags.push(...ice); lowerer.runtimeFences.push(...captured.filter((d) => d.code !== "SC9001")); const first = captured.find((d) => d.code !== "SC9001"); + // Property writes, not literal element writes: the class method + // census reads `x["kind"] = …` as a possible write to every method + // named `kind`, which would make those methods dynamic in native builds. const mutable = e.stmt as unknown as Record; - delete mutable["value"]; - mutable["kind"] = "runtimeFence"; - mutable["code"] = first?.code ?? "SC1090"; - mutable["message"] = first + delete mutable.value; + mutable.kind = "runtimeFence"; + mutable.code = first?.code ?? "SC1090"; + mutable.message = first ? `${first.message} [${first.code}]` : "this return value has no lowering onto the instance's settled return type [SC1090]"; } diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 1502f7fc5..ec946bbfc 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -15,7 +15,7 @@ import { isJsSourceFile, locOf } from "../program.js"; import { typeKey } from "../type-mapper.js"; import { PoisonError, dynFallbackType, neverTaintedJsType, newFnCtx } from "./lowerer.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; -import { findGenericMethodOn, type ClassInfo } from "./lower-classes.js"; +import { findGenericMethodOn, storedClassValueType, type ClassInfo } from "./lower-classes.js"; import { nsPathPrefix } from "./lower-namespaces.js"; import { declSymbolOf } from "./lower-modules.js"; import { npmStaticPackageOfPath } from "../npm-static.js"; @@ -292,6 +292,8 @@ export function genericCallInstance( (_param, i) => expr.arguments[i] ?? expr, ); const retTs = lowerer.checker.getReturnTypeOfSignature(rsig); + const local = localClassResultInstance(lowerer, expr, info, rsig, params, retTs); + if (local) return local; const mappedReturn = lowerer.mapTypeOf(retTs); if (!mappedReturn) { fenceGenericSignatureResult(lowerer, expr, retTs); @@ -339,6 +341,150 @@ export function genericCallInstance( ); } +/** A generic factory whose result is one of its own local classes + * (`function base(v: T) { return class { value: T = v }; }`). Each + * instance has its own class, so the instance is keyed on its bindings and + * the result maps inside it, where the local class takes the instance's + * name and bindings. */ +function localClassResultInstance( + lowerer: Lowerer, + expr: ts.CallExpression, + info: GenericFnInfo, + rsig: ts.Signature, + params: ParamShape[], + retTs: ts.Type, +): GenericInstance | null { + if (!localClassOfResult(lowerer, info, retTs)) return null; + const tsBindings = new Map(); + const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); + const bindingKey = info.typeParams + .map((tp) => { + const bound = bindings.get(tp); + return bound ? typeKey(bound) : "?"; + }) + .join(","); + const pending: IrType = { kind: "void" }; + const inst = internGenericInstance(lowerer, expr, info, params, pending, () => bindings, { + extraKey: `@class<${bindingKey}>`, + tsBindings, + }); + if (inst.returnType !== pending) return inst; + const returnType = withInstanceScope(lowerer, info, inst, () => lowerer.mapTypeOf(retTs)); + if (!returnType) { + fenceGenericSignatureResult(lowerer, expr, retTs); + lowerer.badType(expr, retTs); + } + inst.returnType = returnType; + // Values of the class outside the factory (`const b = new B()`, `b.read()`) + // carry the instantiated checker type; record which class it names. A + // call inside another generic instance can have a type that depends on + // that instance's parameters, which no single class answers. + if (lowerer.typeParamBindings !== null) return inst; + const instance = + returnType.kind === "classval" + ? lowerer.checker + .getConstructSignatures(retTs) + .map((sig) => lowerer.checker.getReturnTypeOfSignature(sig)) + : [retTs]; + const className = + returnType.kind === "classval" || returnType.kind === "object" ? returnType.className : null; + if (className !== null) + for (const type of instance) lowerer.localClassInstanceTypes.set(type.id, className); + return inst; +} + +/** The class value a call of a generic factory returns when the result is + * one of the factory's local classes (`class C extends base(42) {}`), or + * null for any other call. */ +export function genericLocalClassResult(lowerer: Lowerer, expr: ts.CallExpression): IrType | null { + if (!ts.isIdentifier(expr.expression)) return null; + const info = lowerer.genericFnOf(expr.expression); + if (!info || info.implicitParams) return null; + const rsig = lowerer.checker.getResolvedSignature(expr); + if (!rsig || !localClassOfResult(lowerer, info, lowerer.checker.getReturnTypeOfSignature(rsig))) + return null; + return genericCallInstance(lowerer, expr, info).returnType; +} + +/** The type of a `const` binding whose initializer calls a generic factory + * returning one of its local classes (`const B = base(7)`), or constructs + * such a class (`const b = new B()`); null for any other binding. */ +export function genericLocalClassBinding(lowerer: Lowerer, node: ts.Node): IrType | null { + const decl = node.parent; + if ( + !ts.isIdentifier(node) || + !decl || + !ts.isVariableDeclaration(decl) || + decl.name !== node || + !decl.initializer || + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0 + ) + return null; + // An `any` binding keeps the checked-dynamic representation. + if ((lowerer.typeOf(node).flags & ts.TypeFlags.Any) !== 0) return null; + let init = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (ts.isCallExpression(init)) return genericLocalClassResult(lowerer, init); + if (!ts.isNewExpression(init)) return null; + const constructed = storedClassValueType(lowerer, init.expression); + if (constructed?.kind !== "classval") return null; + const classDecl = lowerer.classes.get(constructed.className)?.decl; + return classDecl && insideGenericFunction(classDecl) + ? { kind: "object", className: constructed.className } + : null; +} + +/** Whether `node` is declared inside a function with type parameters. */ +function insideGenericFunction(node: ts.Node): boolean { + for (let parent = node.parent; parent && !ts.isSourceFile(parent); parent = parent.parent) + if (ts.isFunctionLike(parent) && (parent.typeParameters?.length ?? 0) > 0) return true; + return false; +} + +/** The class declared inside a generic function that `type` is the + * constructor or instance type of, or null. */ +function localClassOfResult( + lowerer: Lowerer, + info: GenericFnInfo, + type: ts.Type, +): ts.ClassLikeDeclaration | null { + const symbol = type.getSymbol(); + if (!symbol || !(symbol.flags & ts.SymbolFlags.Class)) return null; + const decl = lowerer.checker.valueDeclarationOf(symbol); + if (!decl || !(ts.isClassExpression(decl) || ts.isClassDeclaration(decl))) return null; + for (let parent = decl.parent; parent && !ts.isSourceFile(parent); parent = parent.parent) + if (parent === info.decl) return decl; + return null; +} + +/** Runs `fn` under an instance's type bindings and local-class names, as + * its body lowers. */ +function withInstanceScope( + lowerer: Lowerer, + info: GenericFnInfo, + inst: GenericInstance, + fn: () => T, +): T { + const prevBindings = lowerer.typeParamBindings; + const prevTsBindings = lowerer.typeParamTsBindings; + const previousPath = lowerer.genericInstantiationPath; + const prevContext = lowerer.instantiationContext; + lowerer.typeParamBindings = inst.bindings; + lowerer.typeParamTsBindings = inst.tsBindings ?? null; + lowerer.genericInstantiationPath = inst.path; + lowerer.instantiationContext = `instantiating '${info.baseName}' with ${inst.typeArgsText}`; + lowerer.localClassInstantiations.push({ owner: info.decl, name: inst.name }); + try { + return fn(); + } finally { + lowerer.localClassInstantiations.pop(); + lowerer.typeParamBindings = prevBindings; + lowerer.typeParamTsBindings = prevTsBindings; + lowerer.genericInstantiationPath = previousPath; + lowerer.instantiationContext = prevContext; + } +} + /** The one instance table both instantiation routes share: key identity IS * signature identity, so a call (`identity(1)`) and a pinned VALUE * (`const f: (x: number) => number = identity`) reuse one compiled diff --git a/packages/compiler/src/frontend/lowering/js-dynamic-objects.test.ts b/packages/compiler/src/frontend/lowering/js-dynamic-objects.test.ts new file mode 100644 index 000000000..23ebdfaa9 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/js-dynamic-objects.test.ts @@ -0,0 +1,38 @@ +import { afterAll, expect, test } from "vitest"; +import * as ts from "../ts7/adapter.js"; +import { closeSourceParser, parseSourceFile } from "../ts7/source-parser-node.js"; +import { isValueReturningConstructorClass } from "./js-dynamic-objects.js"; + +afterAll(closeSourceParser); + +function classify(source: string): boolean { + const file = parseSourceFile("classes.js", source, "js"); + let decl: ts.ClassDeclaration | undefined; + ts.walkPreorder(file, (node) => { + if (!decl && ts.isClassDeclaration(node)) decl = node; + }); + if (!decl) throw new Error("fixture has no class"); + return isValueReturningConstructorClass(decl); +} + +test("a constructor-only class returning a value is claimed", () => { + expect(classify(`class Chalk { constructor(options) { return chalkFactory(options); } }`)).toBe( + true, + ); + expect(classify(`class C { constructor(a) { if (a) return { a }; return [a]; } ; }`)).toBe(true); + expect(classify(`class C { constructor() { const f = () => 1; return { f }; } }`)).toBe(true); +}); + +test("classes that keep their instance layout are left alone", () => { + // No value return, or only nested functions return. + expect(classify(`class C { constructor() { return; } }`)).toBe(false); + expect(classify(`class C { constructor() { function f() { return 1; } f(); } }`)).toBe(false); + expect(classify(`class C { constructor() { const f = () => { return 1; }; f(); } }`)).toBe(false); + // Other members, heritage, or a constructor that observes `this`. + expect(classify(`class C { constructor() { return {}; } m() {} }`)).toBe(false); + expect(classify(`class C { x = 1; constructor() { return {}; } }`)).toBe(false); + expect(classify(`class C extends B { constructor() { super(); return {}; } }`)).toBe(false); + expect(classify(`class C { constructor() { this.a = 1; return {}; } }`)).toBe(false); + expect(classify(`class C { constructor() { const g = () => this; return { g }; } }`)).toBe(false); + expect(classify(`class C {}`)).toBe(false); +}); diff --git a/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts new file mode 100644 index 000000000..1d7ab6eee --- /dev/null +++ b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts @@ -0,0 +1,312 @@ +/* JavaScript declarations whose values only the checked-dynamic + * representation can hold faithfully. TypeScript infers a fixed object + * type for each of them, but the program changes the value's shape or + * identity at run time, so a static layout would answer stale members + * (or throw on a value that no longer fits): + * + * - a non-empty object literal bound to a variable that the same file + * grows or reshapes dynamically: a keyed write with a runtime key + * (`o[k] = v`), a `delete`, or `Object.defineProperty(o, ...)` / + * `Object.defineProperties(o, ...)` (ansi-styles' `assembleStyles`). + * The object literals nested in its property values belong to the same + * value graph and are marked with it; + * - a class whose only member is a constructor that returns a value and + * never reads `this` (chalk's `class Chalk { constructor(options) { + * return chalkFactory(options); } }`): `new C()` answers whatever the + * constructor returns, so its instance type has no fixed layout. + * + * The type mapper answers the checked-dynamic type for the instance and + * object-literal types these declarations own (TypeMapperCtx + * .jsDynamicDeclaration). The scan is syntactic, runs over JavaScript + * files only, and an empty answer leaves every mapping unchanged, so + * TypeScript programs and JavaScript that never uses these patterns + * compile exactly as before. */ +import * as ts from "../ts7/adapter.js"; +import { isJsSourceFile, locOf } from "../program.js"; +import { BOOL, DYN, type IrExpr, type IrStmt } from "../../ir/ir.js"; +import { dynUndefinedExpr, nodeThrowExpr } from "../../ir/build.js"; +import type { Lowerer } from "./lowerer.js"; +import type { ParamShape } from "./call-signatures.js"; +import { newFnCtx } from "./function-context.js"; + +const SCANNED_KINDS: ReadonlySet = new Set([ + ts.SyntaxKind.VariableDeclaration, + ts.SyntaxKind.ClassDeclaration, + ts.SyntaxKind.BinaryExpression, + ts.SyntaxKind.DeleteExpression, + ts.SyntaxKind.CallExpression, +]); + +function unparenthesized(node: ts.Expression): ts.Expression { + while (ts.isParenthesizedExpression(node)) node = node.expression; + return node; +} + +function isLiteralKey(node: ts.Expression): boolean { + const key = unparenthesized(node); + return ( + ts.isStringLiteral(key) || ts.isNumericLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key) + ); +} + +function isAssignmentOperator(kind: ts.SyntaxKind): boolean { + return kind >= ts.SyntaxKind.FirstAssignment && kind <= ts.SyntaxKind.LastAssignment; +} + +/** `class C { constructor(...) { ...; return value; } }` with nothing else: + * no heritage, decorators or other members, a constructor body that + * returns a value at its own level and never mentions `this`/`super`. */ +export function isValueReturningConstructorClass(decl: ts.ClassDeclaration): boolean { + if (decl.heritageClauses !== undefined && decl.heritageClauses.length > 0) return false; + if (decl.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.Decorator)) return false; + let ctor: ts.ConstructorDeclaration | undefined; + for (const member of decl.members) { + if (ts.isSemicolonClassElement(member)) continue; + if (!ts.isConstructorDeclaration(member) || ctor !== undefined) return false; + ctor = member; + } + if (ctor?.body === undefined) return false; + let returnsValue = false; + let observesThis = false; + const visit = (node: ts.Node): void => { + if (observesThis) return; + if (node.kind === ts.SyntaxKind.ThisKeyword || node.kind === ts.SyntaxKind.SuperKeyword) { + observesThis = true; + return; + } + // Arrow functions share the constructor's `this`; other functions + // have their own `this` and their own returns. + if (ts.isFunctionLike(node) && !ts.isArrowFunction(node)) return; + if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; + if (ts.isReturnStatement(node) && node.expression !== undefined && !insideArrow(node, ctor!)) + returnsValue = true; + node.forEachChild(visit); + }; + ctor.body.forEachChild(visit); + for (const parameter of ctor.parameters) parameter.forEachChild(visit); + return returnsValue && !observesThis; +} + +function insideArrow(node: ts.Node, ctor: ts.ConstructorDeclaration): boolean { + for (let p = node.parent; p !== undefined && p !== ctor; p = p.parent) { + if (ts.isArrowFunction(p)) return true; + } + return false; +} + +function markNested(literal: ts.ObjectLiteralExpression, out: Set): void { + if (out.has(literal)) return; + out.add(literal); + for (const property of literal.properties) { + if (!ts.isPropertyAssignment(property)) continue; + const value = unparenthesized(property.initializer); + if (ts.isObjectLiteralExpression(value)) markNested(value, out); + } +} + +export function jsDynamicDeclarations( + files: readonly ts.SourceFile[], + checker: ts.TypeChecker, +): Set { + const out = new Set(); + for (const file of files) { + if (!isJsSourceFile(file) || file.isDeclarationFile) continue; + const literals = new Map(); + const reshaped = new Set(); + const receiver = (expression: ts.Expression | undefined): void => { + if (expression === undefined) return; + const target = unparenthesized(expression); + if (!ts.isIdentifier(target)) return; + const symbol = checker.getSymbolAtLocation(target); + if (symbol !== undefined) reshaped.add(symbol); + }; + ts.walkPreorder( + file, + (node) => { + if (ts.isVariableDeclaration(node)) { + if (!ts.isIdentifier(node.name) || node.initializer === undefined) return; + const init = unparenthesized(node.initializer); + // An empty literal's binding is already checked-dynamic + // (jsOpenObjectType), and its type describes no layout. + if (!ts.isObjectLiteralExpression(init) || init.properties.length === 0) return; + const symbol = checker.getSymbolAtLocation(node.name); + if (symbol !== undefined) literals.set(symbol, init); + return; + } + if (ts.isClassDeclaration(node)) { + if (isValueReturningConstructorClass(node)) out.add(node); + return; + } + if (ts.isBinaryExpression(node)) { + if (!isAssignmentOperator(node.operatorToken.kind)) return; + const left = unparenthesized(node.left); + if (ts.isElementAccessExpression(left) && !isLiteralKey(left.argumentExpression)) + receiver(left.expression); + return; + } + if (ts.isDeleteExpression(node)) { + const operand = unparenthesized(node.expression); + if (ts.isElementAccessExpression(operand) || ts.isPropertyAccessExpression(operand)) + receiver(operand.expression); + return; + } + if (ts.isCallExpression(node)) { + const callee = unparenthesized(node.expression); + if ( + ts.isPropertyAccessExpression(callee) && + ts.isIdentifier(callee.expression) && + callee.expression.text === "Object" && + (callee.name.text === "defineProperty" || callee.name.text === "defineProperties") + ) + receiver(node.arguments[0]); + } + }, + SCANNED_KINDS, + ); + for (const symbol of reshaped) { + const literal = literals.get(symbol); + if (literal !== undefined) markNested(literal, out); + } + } + return out; +} + +/** `new C(args)` for a value-returning constructor class (above): the + * constructor body runs as an ordinary JavaScript function whose result + * is the construction's value. A returned object or function is Node's + * answer; a fall-through or primitive return would answer the bare + * instance, which has no static home here, and throws the deferred + * refusal instead. Null when the callee is not such a class. */ +export function lowerValueReturningConstructorNew( + lowerer: Lowerer, + expr: ts.NewExpression, +): IrExpr | null { + const isDynamic = lowerer.typeCtx.jsDynamicDeclaration; + if (isDynamic === undefined) return null; + const callee = unparenthesized(expr.expression); + if (!ts.isIdentifier(callee)) return null; + let symbol = lowerer.resolveValueSymbol(callee); + if (symbol !== null && (symbol.flags & ts.SymbolFlags.Alias) !== 0) + symbol = lowerer.checker.getAliasedSymbol(symbol); + const decl = symbol + ? lowerer.checker + .declarationsOf(symbol) + .find((node): node is ts.ClassDeclaration => ts.isClassDeclaration(node) && isDynamic(node)) + : undefined; + if (decl === undefined) return null; + const loc = locOf(expr); + const ctor = decl.members.find(ts.isConstructorDeclaration)!; + const simple = ctor.parameters.every( + (p) => ts.isIdentifier(p.name) && p.dotDotDotToken === undefined && p.initializer === undefined, + ); + if (!simple || !ts.isSourceFile(decl.parent)) { + lowerer.unsupported( + "SC1090", + expr, + "constructing a class whose constructor returns a value, with parameter defaults, rest or patterns, or declared inside a function", + ); + } + const args = expr.arguments ?? []; + if (args.some(ts.isSpreadElement)) { + lowerer.unsupported( + "SC1090", + expr, + "spread arguments when constructing a class whose constructor returns a value", + ); + } + const key = `ctorValue:${decl.getSourceFile().fileName}:${decl.getStart()}`; + let name = lowerer.valueHelpers.get(key); + if (name === undefined) { + name = `%ctorValue.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, name); + const savedClass = lowerer.currentClass; + lowerer.currentClass = null; + lowerer.fnStack.push(newFnCtx(false, null, null, DYN)); + try { + const shapes: ParamShape[] = ctor.parameters.map(() => ({ type: DYN, mode: "required" })); + const declared = lowerer.declareParams(ctor.parameters, shapes); + const body: IrStmt[] = [ + ...declared.prologue, + ...lowerer.lowerStmts(ctor.body!.statements), + { + 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}]`, + loc: locOf(ctor), + }, + ]; + lowerer.liftedFns.push({ + name, + params: declared.params, + returnType: DYN, + locals: lowerer.ctx.locals, + body, + loc: locOf(ctor), + }); + } finally { + lowerer.fnStack.pop(); + lowerer.currentClass = savedClass; + } + } + const lowered = ctor.parameters.map((_, i) => + args[i] !== undefined ? lowerer.lowerExprExpecting(args[i]!, DYN) : dynUndefinedExpr(loc), + ); + // Surplus arguments still evaluate, left to right, after the others. + const surplus: IrStmt[] = args + .slice(ctor.parameters.length) + .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 objectLike: IrExpr = { + kind: "logical", + op: "||", + left: { kind: "dynTest", test: "function", value, type: BOOL, loc }, + right: { + kind: "logical", + op: "&&", + left: { kind: "dynTest", test: "object", value, type: BOOL, loc }, + right: { kind: "dynTest", test: "null", negated: true, value, type: BOOL, loc }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + const args0: IrStmt[] = []; + const argLocals = lowered.map((arg) => { + const local = lowerer.declareHiddenLocal("%constructArg", DYN); + args0.push({ kind: "varDecl", localId: local.id, init: arg, loc }); + return { kind: "varRef", localId: local.id, type: DYN, loc } as IrExpr; + }); + return { + kind: "seqExpr", + stmts: [ + ...args0, + ...surplus, + { + kind: "varDecl", + localId: result.id, + init: { kind: "call", callee: name, args: argLocals, type: DYN, loc }, + loc, + }, + ], + result: { + kind: "ternary", + cond: objectLike, + then: value, + else_: nodeThrowExpr( + 0, + "SC1090", + `constructors that return no object are not supported yet [SC1090 at ${sf.fileName}:${line}]`, + DYN, + loc, + ), + type: DYN, + loc, + }, + type: DYN, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts index a9df9e662..c05430ad3 100644 --- a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts +++ b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts @@ -23,6 +23,7 @@ import { builtinModulesArrayLit, stdlibGlobalNameOf, } from "./surfaces.js"; +import { CRYPTO_CONSTANTS } from "./crypto-tables.js"; const PROTOTYPE_METHODS: Record = { Date: ["getTime", "valueOf", "toISOString", "toJSON"], @@ -680,3 +681,34 @@ export function lowerBuiltinLoaderValue(lowerer: Lowerer, loc: SrcLoc): IrExpr { loc, }; } + +/** The deprecated node:constants module object: the runtime merges + * os.constants' groups and fs.constants with crypto.constants, which a + * lifted function answers from the baked table the first time the object + * is built. */ +export function builtinConstantsObject(lowerer: Lowerer, loc: SrcLoc): IrExpr { + const name = "%builtin.constants.crypto"; + if (!lowerer.builtinCallableValueFns.has(name)) { + lowerer.builtinCallableValueFns.set(name, name); + const fields = Object.entries(CRYPTO_CONSTANTS).map(([key, value]) => ({ + key: strLit(key, loc), + value: box(builtinConstLit(value, loc)), + })); + lowerer.liftedFns.push({ + name, + params: [], + returnType: DYN, + locals: [], + body: [{ kind: "return", value: { kind: "dynObjLit", fields, type: DYN, loc }, loc }], + loc, + }); + } + const table: IrExpr = { + kind: "closure", + fnName: name, + captures: [], + type: { kind: "func", params: [], ret: DYN }, + loc, + }; + return { kind: "libCall", fn: "constants.object", args: [box(table)], type: DYN, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 3260c0484..977137f69 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -24,6 +24,13 @@ import { lowerZlibStreamMethodCall, } from "./builtins/compression.js"; import type { Lowerer } from "./lowerer.js"; +import { + inlineNamespaceRequireOf, + lowerInlineRequireValue, + lowerUncompiledRequireCall, + lowerWithInlineRequireInit, +} from "./cjs-inline-require.js"; +import { cjsExportCallIgnoresThis, cjsInFileExportGlobalOf } from "./cjs-exports-object.js"; import { lowerGenMethodCall } from "./lower-generators.js"; import { lowerClassSymbolMethodCall } from "./symbol-methods.js"; import { errorToStringCall } from "./error-methods.js"; @@ -34,6 +41,7 @@ import { BOOL, CAUGHT, DYN, + DYN_HANDLE_KINDS, F64, type IrExpr, type IrFunction, @@ -61,7 +69,11 @@ import { lowerProtocolMethodCall } from "./class-protocol-dispatch.js"; import { isJsSourceFile, locOf, requireSpecOf } from "../program.js"; import { lowerJsonRequireCall } from "./json-require.js"; import { lowerAssertInstanceCall } from "./assert-instances.js"; -import { isGenericCallableMemberType, typeKey } from "../type-mapper.js"; +import { + isGenericCallableMemberType, + isOverloadedCallableMemberType, + typeKey, +} from "../type-mapper.js"; import { dynFallbackType, jsFuncNameOf, newFnCtx, staticImportNamespaceType } from "./lowerer.js"; import { NARROW_FIRST, @@ -79,7 +91,10 @@ import { import { lowerArrayFromAsyncCall } from "./containers/array-from-async.js"; import { lowerDynArrayFilterCall, lowerDynArrayFlatMapCall } from "./containers/array-callbacks.js"; import { lowerGroupByStaticCall } from "./containers/collection-grouping.js"; -import { lowerIteratorHelperCall } from "./containers/iterator-helpers.js"; +import { + lowerGeneratorHelperCall, + lowerIteratorHelperCall, +} from "./containers/iterator-helpers.js"; import { lowerObjectAssignIndexShape, lowerObjectConstructor, @@ -234,6 +249,32 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const json = lowerJsonRequireCall(lowerer, expr); if (json) return json; + // Inline require() of a program module (cjs-inline-require.ts): a + // member call over its export table resolves like a binding's member + // call after the module's %init at this position; the require itself + // as a value answers the module's whole-export value. + if (ts.isPropertyAccessExpression(expr.expression) && !expr.expression.questionDotToken) { + const req = inlineNamespaceRequireOf(lowerer, expr.expression.expression); + if (req !== null && !lowerer.inlineRequireInitDone.has(req.call)) { + // Node calls the member with `this` bound to the module's exports. + if ( + ts.isIdentifier(expr.expression.name) && + !cjsExportCallIgnoresThis(lowerer, req.dep, expr.expression.name.text) + ) { + lowerer.unsupported( + "SC1090", + expr, + `calling '${expr.expression.name.text}' through require(${JSON.stringify(req.spec)}) when the function may read 'this' (Node binds it to the module's exports object, which compiles to a static table — bind the module and call a local function instead)`, + ); + } + return lowerWithInlineRequireInit(lowerer, req, () => lowerCall(lowerer, expr)); + } + } + { + const inlineValue = lowerInlineRequireValue(lowerer, expr); + if (inlineValue) return inlineValue; + } + // 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): @@ -310,6 +351,12 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerImportMetaResolveCall(lowerer, expr) ?? lowerRequireResolveCall(lowerer, expr); if (resolved) return resolved; } + // Any other require() call of the CommonJS global: MODULE_NOT_FOUND for + // packages nothing installed resolves, a named refusal otherwise. + { + const uncompiled = lowerUncompiledRequireCall(lowerer, expr); + if (uncompiled) return uncompiled; + } // `process.getuid?.()` — intercepted BEFORE the optional-chain // machinery (the member always exists on a POSIX target, so the @@ -1274,6 +1321,32 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); return { kind: "callValue", callee, args, type: callee.type.ret, loc }; } + // `exports.f(x)` / `module.exports.f(x)` inside the module: a call of + // the member's export global (cjs-exports-object.ts), the value every + // `exports.f =` statement assigns. + if (ts.isPropertyAccessExpression(expr.expression) && !expr.questionDotToken) { + const g = cjsInFileExportGlobalOf(lowerer, expr.expression); + if ( + g && + (g.type.kind === "func" || g.type.kind === "dyn") && + !cjsExportCallIgnoresThis(lowerer, expr.getSourceFile(), 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)`, + ); + } + if (g && (g.type.kind === "func" || g.type.kind === "dyn")) { + const callee: IrExpr = { kind: "varRef", localId: g.id, type: g.type, loc }; + const checked = lowerCheckedCallableValue(lowerer, expr, callee, loc); + if (checked) return checked; + if (callee.type.kind === "func") { + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); + return { kind: "callValue", callee, args, type: callee.type.ret, loc }; + } + } + } if (ts.isPropertyAccessExpression(expr.expression)) { const intrinsic = fenceNodeModuleMutationCall(lowerer, expr, expr.expression) ?? @@ -1421,6 +1494,8 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // array method paths (the terminal names collide with array // methods, but only iterator-typed receivers reach this). lowerIteratorHelperCall(lowerer, expr, expr.expression) ?? + // Helper calls on generators and other iterator objects. + lowerGeneratorHelperCall(lowerer, expr, expr.expression) ?? lowerIteratorStaticFence(lowerer, expr, expr.expression) ?? lowerObjectStaticCall(lowerer, expr, expr.expression) ?? lowerObjectFromEntriesCall(lowerer, expr, expr.expression) ?? @@ -3074,8 +3149,9 @@ function lowerOptionalStringNumber( return { kind: "call", callee: helper, args: [arg], type: F64, loc }; } -/** Iterator types whose helper calls have no lowering on any path (the - * array iterator's chains lower in lowerIteratorHelperCall). */ +/** 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"]); const ITERATOR_HELPER_METHODS = new Set([ "map", @@ -3142,6 +3218,8 @@ function lowerDynReceiverMethodCall( 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, @@ -3152,7 +3230,7 @@ function lowerDynReceiverMethodCall( lowerer.noLowering( `${recvSymbol.name}.${access.name.text}`, call, - "iterator helpers lower on chains from arr.values() or arr.entries() that end in a terminal (toArray, forEach, reduce, some, every, find) — spread the string first: [...s].values()", + "iterator helpers lower on generators and on array, Map and Set iterators — spread the string first: [...s].values()", ); } const arrayReceiver = lowerer.checker.isArrayType(recvTs); @@ -3274,6 +3352,11 @@ function lowerDynReceiverMethodCall( lowerStringMethodCall(lowerer, call, access, () => recv) ); } + // A native handle read through `as any` (`(hash as any).update(x)`) + // dispatches through the handle, as the same value held in an + // 'unknown' slot does. + if (checkerUntyped && DYN_HANDLE_KINDS.has(recv.type.kind)) + recv = lowerer.coerceToExpected(recv, DYN); if (recv.type.kind !== "dyn") return null; if ( access.name.text === "normalize" && @@ -3480,13 +3563,41 @@ function lowerDynReceiverMethodCall( // its name overlaps a primitive prototype (`colors.bold(...)`): the // source value was compiled from that object shape, while genuinely // untyped/any receivers retain the runtime-kind ambiguity and fence. - if (DYN_PROTO_METHOD_NAMES.has(access.name.text) && !ownCallable) return null; + // A name only String.prototype declares (`bold`, `italics`, `link`, + // ...) on a checker-untyped receiver: every other dyn kind answers its + // own or inherited member exactly like a non-prototype name (chalk's + // `chalk.red.bold(...)` reads a prototype getter of a function), so + // only a string receiver keeps the refusal, deferred to run time. + const stringOnlyName = + checkerUntyped && !ownCallable && DYN_STRING_PROTO_ONLY_NAMES.has(access.name.text); + if (DYN_PROTO_METHOD_NAMES.has(access.name.text) && !ownCallable && !stringOnlyName) return null; // Optional forms (`obj.cb?.()`, `obj?.cb()`) belong to the chain // machinery's short-circuit semantics — not modeled here yet. if (call.questionDotToken || access.questionDotToken) return null; const loc = locOf(call); const receiverLocal = lowerer.declareHiddenLocal("%dynCallReceiver", DYN); const receiver: IrExpr = { kind: "varRef", localId: receiverLocal.id, type: DYN, loc }; + // The string refusal throws in place of the member read (a seqExpr + // 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; + 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}]`, + DYN, + loc, + ), + else_: read, + type: DYN, + loc, + }; + }; const member: IrExpr = { kind: "dynKeyGet", key: { kind: "strLit", value: access.name.text, type: STRING, loc: locOf(access) }, @@ -3498,7 +3609,7 @@ function lowerDynReceiverMethodCall( const callee = varRef(calleeLocal.id, DYN, loc); const stmts: IrStmt[] = [ { kind: "varDecl", localId: receiverLocal.id, init: recv, loc }, - { kind: "varDecl", localId: calleeLocal.id, init: member, loc }, + { kind: "varDecl", localId: calleeLocal.id, init: stringRefusal(member), loc }, ]; if (call.arguments.some(ts.isSpreadElement)) { const spread = lowerSpreadArgsCall(lowerer, call, callee, loc); @@ -3607,6 +3718,31 @@ const DYN_PROTO_METHOD_NAMES = new Set([ "toPrecision", ]); +/** The DYN_PROTO_METHOD_NAMES that only String.prototype declares among + * the dyn-representable kinds' prototypes. */ +const DYN_STRING_PROTO_ONLY_NAMES = new Set([ + "anchor", + "big", + "blink", + "bold", + "codePointAt", + "fixed", + "fontcolor", + "fontsize", + "isWellFormed", + "italics", + "link", + "localeCompare", + "normalize", + "small", + "strike", + "sub", + "sup", + "toLocaleLowerCase", + "toLocaleUpperCase", + "toWellFormed", +]); + /** The SHARED prototype names scr_dyn_invoke dispatches at runtime (the * subset of DYN_PROTO_METHOD_NAMES with a receiver-kind dispatch): the * runtime runs the real method for the receiver's kind, throws Node's @@ -4117,10 +4253,32 @@ function lowerNumberToStringCall( } if (call.arguments.length === 1) { if (operand.type.kind !== "f64") return null; + const arg = call.arguments[0]!; + // A number radix needs no box: ToIntegerOrInfinity of a number is a + // truncation, which the typed entry point applies itself. + if (!ts.isSpreadElement(arg) && lowerer.mapTypeOf(lowerer.typeOf(arg))?.kind === "f64") { + const radix = lowerer.lowerExpr(arg); + if (radix.type.kind === "f64") { + return { + kind: "libCall", + fn: "num.toStringRadixF64", + args: [operand, radix], + type: STRING, + loc: locOf(call), + }; + } + return { + kind: "libCall", + fn: "num.toStringRadix", + args: [operand, lowerer.coerceToExpected(radix, DYN)], + type: STRING, + loc: locOf(call), + }; + } return { kind: "libCall", fn: "num.toStringRadix", - args: [operand, lowerer.lowerExprExpecting(call.arguments[0]!, DYN)], + args: [operand, lowerer.lowerExprExpecting(arg, DYN)], type: STRING, loc: locOf(call), }; @@ -6467,9 +6625,9 @@ function recordAssignHelper( } /** The `Iterator` global's statics (ES2025 — Iterator.from, and the - * abstract constructor as a value): no first-class iterator objects - * exist here, so every member fences with the working spelling named - * instead of the generic-method fence's monomorphization wording. */ + * 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. */ function lowerIteratorStaticFence( lowerer: Lowerer, call: ts.CallExpression, @@ -6480,8 +6638,8 @@ function lowerIteratorStaticFence( lowerer.noLowering( `Iterator.${access.name.text}`, call, - "first-class iterator objects have no lowering — iterator helpers compile as one chain on an " + - "array iterator, consumed in place: arr.values().map(f).take(n).toArray()", + "iterator helpers apply directly to generators and to array, Map and Set iterators: " + + "arr.values().map(f).take(n).toArray()", ); } @@ -8132,6 +8290,30 @@ function lowerObjectAssignHybrid(lowerer: Lowerer, call: ts.CallExpression): IrE return { kind: "recordLit", fields, type: mapped, loc }; } +/** The accessed member is an overload set (isOverloadedCallableMemberType), + * also under an optional chain whose access still spells `| undefined`. */ +function isOverloadedMemberAccess(lowerer: Lowerer, access: ts.PropertyAccessExpression): boolean { + return isOverloadedCallableMemberType( + lowerer.checker.getNonNullableType(lowerer.typeOf(access)), + lowerer.checker, + ); +} + +/** The result type of the overload a call through an overloaded member + * resolved to: the erased slot's result converts to it. A non-generic + * overload answers through its declaration, because the resolved signature + * of an optional-chain call (`api?.m(x)`) also carries the chain's + * undefined; a generic one answers through its instantiation. */ +function resolvedOverloadResultType(lowerer: Lowerer, call: ts.CallExpression): IrType { + const { checker } = lowerer; + const signature = checker.getResolvedSignature(call); + const decl = signature ? checker.signatureDeclaration(signature) : undefined; + const declared = decl ? checker.getSignatureFromDeclaration(decl) : undefined; + const source = declared && declared.getTypeParameters().length === 0 ? declared : signature; + const result = source ? lowerer.mapTypeOf(checker.getReturnTypeOfSignature(source)) : null; + return result ?? lowerer.irTypeOf(call); +} + export function lowerRecordFieldCall( lowerer: Lowerer, call: ts.CallExpression, @@ -8163,7 +8345,18 @@ export function lowerRecordFieldCall( const callee = lowerer.lowerUnionProperty(access); if (callee?.type.kind !== "func") return null; const args = completeFuncValueArgs(lowerer, call, callee.type, locOf(call)); - return { kind: "callValue", callee, args, type: callee.type.ret, loc: locOf(call) }; + const value: IrExpr = { + kind: "callValue", + callee, + args, + type: callee.type.ret, + loc: locOf(call), + }; + // An overloaded member's erased result converts to the resolved + // overload's type, as on a single record receiver below. + return isOverloadedMemberAccess(lowerer, access) + ? lowerer.coerceInto(call, value, resolvedOverloadResultType(lowerer, call)) + : value; } const target = lowerer.fieldTarget(access); const loc = locOf(call); @@ -8238,10 +8431,14 @@ export function lowerRecordFieldCall( type: callee.type.ret, loc, }; - // A generic member's field holds its erased instance: the result - // converts to this call's instantiated return type at the call. - if (isGenericCallableMemberType(lowerer.typeOf(access), lowerer.checker)) + // A generic member's field holds its erased instance, and an overloaded + // member's field its erased function (the overloads joined): the result + // converts to this call's instantiated or resolved return type. + const memberType = lowerer.typeOf(access); + if (isGenericCallableMemberType(memberType, lowerer.checker)) result = lowerer.coerceInto(call, result, lowerer.irTypeOf(call)); + else if (isOverloadedMemberAccess(lowerer, access)) + result = lowerer.coerceInto(call, result, resolvedOverloadResultType(lowerer, call)); return init ? { kind: "seqExpr", stmts: [init], result, type: result.type, loc } : result; } diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 7b2d176c3..cddc536c6 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -1,3 +1,4 @@ +import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js"; import { checkedIterableSpread } from "./checked-iterable-spread.js"; import { lowerReadlineUiNew } from "./builtins/readline.js"; @@ -11,7 +12,13 @@ import { InternalCompilerError } from "../../errors.js"; import { fsStreamClassOf, lowerFsStreamNew } from "./builtins/fs-streams.js"; import * as ts from "../ts7/adapter.js"; import { lowerZlibStreamNew, zlibClassName, zlibStreamModeOf } from "./builtins/compression.js"; +import { + type BuiltinClassRef, + builtinClassAliasOf, + builtinClassRefOf, +} from "./builtins/class-values.js"; import type { FnCtx, Lowerer } from "./lowerer.js"; +import { inlineNamespaceRequireOf, lowerWithInlineRequireInit } from "./cjs-inline-require.js"; import { BOOL, DATE_T, @@ -44,6 +51,7 @@ import { implicitAnyParamSymbolsOf, implicitCallInstance, implicitMonoFile, + genericLocalClassResult, type GenericFnInfo, } from "./generic-functions.js"; import { @@ -143,7 +151,7 @@ import { lowerObjectFactoryNew } from "./object-factory-new.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; import { lowerInstanceConstructorNew } from "./class-instance-constructor.js"; import { classPrototypeData } from "./class-prototypes.js"; -import { constructionEscape, mentionsReceiver } from "./construction-escapes.js"; +import { constructionEscape } from "./construction-escapes.js"; import { checkedClassConstruction, checkedClassConstructionPacked } from "./class-construction.js"; import { initializeRuntimeStatics, hasRuntimeStatics } from "./class-runtime-statics.js"; import { reflectedClassStaticMethodValue } from "./class-method-values.js"; @@ -222,6 +230,11 @@ export function storedClassValueType( if (value?.kind === "classval") return value; } } + // A generic factory's own local class has one class per instance. + if (ts.isCallExpression(expression)) { + const local = genericLocalClassResult(lowerer, expression); + if (local?.kind === "classval") return local; + } const type = lowerer.typeOf(expression); const mapped = lowerer.mapTypeOf(type); if (mapped?.kind === "classval" && !lowerer.classes.has(mapped.className)) { @@ -468,11 +481,6 @@ export interface ClassInfo { * unassigned read throws the catchable TypeError instead of yielding * an undefined the declared type cannot hold (SEMANTICS.md). */ deferredInitFields?: Set; - /** Fields (inherited included) that code construction exposes the - * instance to can read before their assignment: the slot is the - * undefined-armed union, allocation writes undefined, and reads yield the - * union value, so an early read sees undefined as in Node. */ - earlyReadFields?: Set; /** True when construction may hand the instance to other code (a method * call, an argument, a callback, or a base constructor that does) — a * subclass's own fields can then be read before their assignment. */ @@ -928,12 +936,15 @@ export function registerBuiltinStreamClasses(lowerer: Lowerer): void { * "stream" ambient module, EXCLUDING @types/node's (whose stream.Readable * also types child stdio — under @types/node the childStream mapping * keeps priority and the static stream classes stand down; the shipped - * fallback declarations are the supported surface). */ + * fallback declarations are the supported surface). A const binding of + * one (`const R = Readable`) names the same class (builtinClassAliasOf). */ export function builtinStreamInfoOf( lowerer: Lowerer, symbol: ts.Symbol | null | undefined, ): ClassInfo | null { if (!symbol) return null; + const alias = builtinClassAliasOf(lowerer, symbol); + if (alias !== null) return alias.kind === "stream" ? alias.info : null; if (!lowerer.isStdlibSymbol(symbol)) { // A const ALIAS of a namespace member (`const Writable = // stream.Writable` — the two-step spelling; the one-step @@ -1288,12 +1299,16 @@ function undefArmedFieldType(lowerer: Lowerer, p: ts.Symbol): IrType | null { * `extends EventEmitter`, `x instanceof EventEmitter`) — any import * spelling (named/default/namespace member, CJS require) resolves to * the ambient class. Provenance-checked like the error classes: only a - * stdlib-file declaration inside the "events" ambient module counts. */ + * stdlib-file declaration inside the "events" ambient module counts. A + * const binding of it (`const E = EventEmitter`) names the same class + * (builtinClassAliasOf). */ export function builtinEmitterInfoOf( lowerer: Lowerer, symbol: ts.Symbol | null | undefined, ): ClassInfo | null { if (!symbol) return null; + const alias = builtinClassAliasOf(lowerer, symbol); + if (alias !== null) return alias.kind === "emitter" ? alias.info : null; if (!lowerer.isStdlibSymbol(symbol)) { // A const ALIAS of the emitter class member (`const EventEmitter = // require('node:events').EventEmitter` — commander's spelling; the @@ -1334,12 +1349,17 @@ export function builtinEmitterInfoOf( /** The builtin error ClassInfo a VALUE symbol refers to (`new Error`, * `extends TypeError`, `x instanceof RangeError`), or null. Provenance- * checked: only the standard library's declarations count — a user's own - * `class Error` resolves through classBySymbol instead. */ + * `class Error` resolves through classBySymbol instead. A const binding + * of one (`const E = TypeError`) names the same class + * (builtinClassAliasOf). */ export function builtinErrorInfoOf( lowerer: Lowerer, symbol: ts.Symbol | null | undefined, ): ClassInfo | null { - if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; + if (!symbol) return null; + const alias = builtinClassAliasOf(lowerer, symbol); + 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; } @@ -3507,54 +3527,55 @@ export function collectClassShapeInner( const derived = decl.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword) ?? false; - // A generic family is not emitted; its instances and each mixin - // application analyze their own construction with their actual base. - const escape: { escapes: boolean; assigned: Set | null } = familyMode - ? { escapes: derived, assigned: null } - : constructionEscape({ - ctor, - fields: fieldOrder.flatMap((f) => - paramProps.some((pp) => pp.name === f.name) - ? [] - : [{ name: f.name, initializer: f.initializer }], - ), - paramProps: paramProps.map((pp) => pp.name), - derived, - baseEscapes: - base === null || - callableBase !== undefined || - factoryBaseExpression !== undefined || - base.constructionEscapes === true, - isField: (name) => - fields.has(name) && - !methods.has(`set:${name}`) && - !lowerer.findMethodOn(base, `set:${name}`), - privateMethodBody: (call) => { - const callee = call.expression; - if (!ts.isPropertyAccessExpression(callee)) return null; - const name = callee.name.text; - const declaration = decl.members.find( - (member): member is ts.MethodDeclaration => - ts.isMethodDeclaration(member) && - member.body !== undefined && - (ts.isIdentifier(member.name) || ts.isPrivateIdentifier(member.name)) && - member.name.text === name, - ); - if (!declaration?.body || declaration.asteriskToken) return null; - const modifiers = ts.getModifiers(declaration) ?? []; - if ( - modifiers.some( - (m) => - m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.AsyncKeyword, + // Generic instances and mixins share layouts across declarations, so + // they only report whether construction exposes the instance. + const escape: { escapes: boolean; assigned: Set | null } = + familyMode || mixin + ? { escapes: derived, assigned: null } + : constructionEscape({ + ctor, + fields: fieldOrder.flatMap((f) => + paramProps.some((pp) => pp.name === f.name) + ? [] + : [{ name: f.name, initializer: f.initializer }], + ), + paramProps: paramProps.map((pp) => pp.name), + derived, + baseEscapes: + base === null || + callableBase !== undefined || + factoryBaseExpression !== undefined || + base.constructionEscapes === true, + isField: (name) => + fields.has(name) && + !methods.has(`set:${name}`) && + !lowerer.findMethodOn(base, `set:${name}`), + privateMethodBody: (call) => { + const callee = call.expression; + if (!ts.isPropertyAccessExpression(callee)) return null; + const name = callee.name.text; + const declaration = decl.members.find( + (member): member is ts.MethodDeclaration => + ts.isMethodDeclaration(member) && + member.body !== undefined && + (ts.isIdentifier(member.name) || ts.isPrivateIdentifier(member.name)) && + member.name.text === name, + ); + if (!declaration?.body || declaration.asteriskToken) return null; + const modifiers = ts.getModifiers(declaration) ?? []; + if ( + modifiers.some( + (m) => + m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.AsyncKeyword, + ) ) - ) - return null; - const isPrivate = - ts.isPrivateIdentifier(declaration.name) || - modifiers.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword); - return isPrivate ? declaration.body : null; - }, - }); + return null; + const isPrivate = + ts.isPrivateIdentifier(declaration.name) || + modifiers.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword); + return isPrivate ? declaration.body : null; + }, + }); // The deferred definite-assignment check: a field on the unguarded // list passes only with an unconditional `this.x = ...` at the // constructor's TOP LEVEL — the same standard the JS-class path @@ -3610,30 +3631,18 @@ export function collectClassShapeInner( } } - // Fields assigned after construction can expose the instance (a method - // call, a base constructor that calls an override) can be read while - // Node still holds undefined. Their slots admit undefined, allocation - // writes it, and reads yield it, as in Node. - const earlyReadFields = new Set(base?.earlyReadFields ?? []); - if (escape.assigned && escape.escapes) { + if (escape.assigned && !inst) { for (const f of fieldOrder) { if (f.redeclared || escape.assigned.has(f.name) || deferredInitFields.has(f.name)) continue; + if (paramProps.some((pp) => pp.name === f.name)) continue; const declared = fields.get(f.name); - if (declared === undefined || !earlyReadable(lowerer, declared)) continue; - // A declared `T | undefined` field already starts undefined. - if (declared.kind === "union" && lowerer.armTag(declared.unionId, UNDEFINED_T) >= 0) + if (declared?.kind !== "object" || lowerer.classes.get(declared.className)?.def.runtime) continue; - const armed = lowerer.runtimeOptionalType(declared); - if (armed.kind !== "union") continue; + const armed = lowerer.withUndefinedArm(declared); + if (armed === null || armed.kind !== "union") continue; fields.set(f.name, armed); f.type = armed; - // Primitive and union slots yield the undefined value itself. - // Reference slots keep the deferred-init reads: consumers that - // observe undefined (printing, equality, typeof, nullish defaults) - // see it, and member access on it throws a TypeError, as in Node. - if (declared.kind === "union" || PRIMITIVE_EARLY_READS.has(declared.kind)) - earlyReadFields.add(f.name); - else deferredInitFields.add(f.name); + deferredInitFields.add(f.name); } } @@ -3738,43 +3747,6 @@ export function collectClassShapeInner( (name) => !hasAccessor(name) && !methods.has(name) && !lowerer.findMethodOn(base, name), ) : null; - // Constructor statements that run after the instance may have been - // exposed (a method call, `this` passed along, a base constructor - // that exposes it): a field they create can be read before its - // assignment, while Node holds undefined. - const exposedBefore = new Set(); - { - let exposed = false; - for (const stmt of ctor?.body?.statements ?? []) { - const expression = ts.isExpressionStatement(stmt) ? stmt.expression : null; - if ( - expression && - ts.isCallExpression(expression) && - expression.expression.kind === ts.SyntaxKind.SuperKeyword - ) { - if (base?.constructionEscapes === true || expression.arguments.some(mentionsReceiver)) - exposed = true; - continue; - } - const assigned = - expression && - ts.isBinaryExpression(expression) && - expression.operatorToken.kind === ts.SyntaxKind.EqualsToken && - ts.isPropertyAccessExpression(expression.left) && - expression.left.expression.kind === ts.SyntaxKind.ThisKeyword - ? expression - : null; - if (exposed || (assigned && mentionsReceiver(assigned.right))) exposedBefore.add(stmt); - const plainAssignment = - assigned !== null && - ts.isPropertyAccessExpression(assigned.left) && - !mentionsReceiver(assigned.right) && - !hasAccessor(assigned.left.name.text) && - !methods.has(assigned.left.name.text) && - !lowerer.findMethodOn(base, assigned.left.name.text); - if (!plainAssignment && mentionsReceiver(stmt)) exposed = true; - } - } if (ctor?.body) { for (const stmt of ctor.body.statements) { const lhs = @@ -3845,15 +3817,8 @@ export function collectClassShapeInner( type = inheritedJsFieldType(lowerer, decl, name, type); // super() can invoke a subclass setter or method before its // constructor assignments run. These slots start undefined. - const sealed = sealedPrefix?.created.has(name) === true; - if (base && lowerer.dynConvertible(type) && !sealed) type = DYN; - if (exposedBefore.has(stmt) && !sealed && earlyReadable(lowerer, type)) { - const armed = lowerer.runtimeOptionalType(type); - if (armed.kind === "union") { - type = armed; - earlyReadFields.add(name); - } - } + if (base && lowerer.dynConvertible(type) && !sealedPrefix?.created.has(name)) + type = DYN; fields.set(name, type); fieldOrder.push({ name, type, initializer: undefined }); continue; @@ -4114,7 +4079,6 @@ export function collectClassShapeInner( ? { classDecorators: { nodes: classDecoratorNodes } } : {}), ...(deferredInitFields.size > 0 ? { deferredInitFields } : {}), - ...(earlyReadFields.size > 0 ? { earlyReadFields } : {}), ...(escape.escapes ? { constructionEscapes: true as const } : {}), }; // GENERIC members get their declaring-class backlink now that the @@ -4419,6 +4383,7 @@ export function lowerStaticFieldInits(lowerer: Lowerer, info: ClassInfo): IrStmt // instantiation's bindings/context (a no-op for everything else — // generic FAMILIES own their statics and carry no genericInstance). const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { return withInstanceBindings(lowerer, info, () => lowerStaticFieldInitsInner(lowerer, info)); } catch (error) { @@ -4426,8 +4391,11 @@ 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()) && lowerer.diagSink === null) { - const fence = lowerer.deferToRuntimeFence(diagsBefore, info.decl, { kind: "statement" }); + if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + const fence = + lowerer.diagSink === null + ? lowerer.deferToRuntimeFence(diagsBefore, info.decl, { kind: "statement" }) + : lowerer.capturedRuntimeFence(sinkMark, info.decl); if (fence) return [fence]; } return []; @@ -4564,6 +4532,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] lowerer.stats.statementsTotal++; lowerer.bumpFileStat(locOf(f.initializer).file, "total"); const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { const previousThis = lowerer.ctx.thisLocal; const receiver = @@ -4599,10 +4568,11 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] if (!(e instanceof PoisonError)) throw e; lowerer.stats.statementsFailed++; lowerer.bumpFileStat(locOf(f.initializer).file, "failed"); - if (isJsSourceFile(f.initializer.getSourceFile()) && lowerer.diagSink === null) { - const fence = lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { - kind: "statement", - }); + if (isJsSourceFile(f.initializer.getSourceFile())) { + const fence = + lowerer.diagSink === null + ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) + : lowerer.capturedRuntimeFence(sinkMark, f.initializer); if (fence) out.push(fence); } } @@ -6289,33 +6259,6 @@ function overrideReturnAbi( return declared; } -/** Field kinds whose early reads yield the undefined value (earlyReadFields). */ -const PRIMITIVE_EARLY_READS: ReadonlySet = new Set([ - "f64", - "bool", - "string", - "bigint", -]); - -/** Field types whose slot can admit undefined for reads before the field's - * assignment. Date values are scalars, so their unassigned slot is memory - * safe; checked-dynamic slots already hold undefined. */ -function earlyReadable(lowerer: Lowerer, type: IrType): boolean { - if (isUnitType(type)) return false; - switch (type.kind) { - case "dyn": - case "void": - case "date": - case "generator": - case "caught": - return false; - case "object": - return lowerer.classes.get(type.className)?.def.runtime !== true; - default: - return true; - } -} - /** The nearest declaration of `name` at or above `info` — the method a * receiver of that static class runs when nothing below overrides it. */ export function findMethodOn( @@ -6502,7 +6445,7 @@ export function collectInheritedJsFieldWrites( * hierarchy identifies the original declarations through aliases as well. */ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.SourceFile[]): void { const families: [ts.MethodDeclaration, ts.MethodDeclaration][] = []; - collectClassMethodMutations(lowerer, files); + lowerer.speculativeTypeQueries(() => collectClassMethodMutations(lowerer, files)); const visit = (node: ts.Node): void => { if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { const accessors = node.members.filter( @@ -7703,6 +7646,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL lowerer.stats.statementsTotal++; lowerer.bumpFileStat(locOf(f.initializer).file, "total"); const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { const value = lowerer.lowerExprExpecting(f.initializer, f.type); out.push({ @@ -7717,10 +7661,11 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL if (!(e instanceof PoisonError)) throw e; lowerer.stats.statementsFailed++; lowerer.bumpFileStat(locOf(f.initializer).file, "failed"); - if (isJsSourceFile(f.initializer.getSourceFile()) && lowerer.diagSink === null) { - const fence = lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { - kind: "statement", - }); + if (isJsSourceFile(f.initializer.getSourceFile())) { + const fence = + lowerer.diagSink === null + ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) + : lowerer.capturedRuntimeFence(sinkMark, f.initializer); if (fence) out.push(fence); } } @@ -7753,11 +7698,7 @@ function paramPropInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrLoca obj: { kind: "varRef", localId: thisLocal.id, type: thisType, loc }, className: info.def.name, field: pp.name, - // An early-readable field's slot admits undefined (earlyReadFields). - value: lowerer.coerceToExpected( - { kind: "varRef", localId: local!.id, type: local!.type, loc }, - info.fields.get(pp.name) ?? local!.type, - ), + value: { kind: "varRef", localId: local!.id, type: local!.type, loc }, loc, }); } @@ -8526,8 +8467,22 @@ function assignedThisFieldType(lowerer: Lowerer, expr: ts.NewExpression): IrType return lowerer.currentClass?.fields.get(parent.left.name.text) ?? null; } +/** Map, Set and the error classes construct in lowerNew's identifier + * branch, which a module member alias of one (`new mod.M()`) also takes. */ +function isCollectionOrErrorClass(ref: BuiltinClassRef | null): boolean { + return ref?.kind === "collection" || ref?.kind === "error"; +} + export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const loc = locOf(expr); + // `new (require("./x").C)()`: the module's %init at the require's + // position, then construction through the member as for a binding. + if (ts.isPropertyAccessExpression(expr.expression) && !expr.expression.questionDotToken) { + const req = inlineNamespaceRequireOf(lowerer, expr.expression.expression); + if (req !== null && !lowerer.inlineRequireInitDone.has(req.call)) { + return lowerWithInlineRequireInit(lowerer, req, () => lowerNew(lowerer, expr)); + } + } const worker = lowerWorkerNew(lowerer, expr); if (worker) return worker; const readlineUi = lowerReadlineUiNew(lowerer, expr); @@ -8551,6 +8506,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { undefined, lowerer.checker.getSymbolAtLocation(expr.expression), ); + const valueReturning = lowerValueReturningConstructorNew(lowerer, expr); + if (valueReturning) return valueReturning; const instanceConstructor = lowerInstanceConstructorNew(lowerer, expr); if (instanceConstructor) return instanceConstructor; const objectFactory = lowerObjectFactoryNew(lowerer, expr); @@ -8818,7 +8775,10 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { ts.isIdentifier(expr.expression) || (ts.isPropertyAccessExpression(expr.expression) && ts.isIdentifier(expr.expression.name) && - lowerer.isStdlibGlobal(expr.expression.expression, "globalThis")) + (lowerer.isStdlibGlobal(expr.expression.expression, "globalThis") || + // `new mod.M()` over an exported `const M = Map` or `const E = + // TypeError` + isCollectionOrErrorClass(builtinClassRefOf(lowerer, expr.expression)))) ) { // `import C = N.C; new C()` — the alias's own source-order guards // (a no-op for every non-import= binding). @@ -9183,7 +9143,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if (symbol?.name === "Array" && lowerer.isStdlibSymbol(symbol)) { return lowerArrayConstructor(lowerer, expr, expr.arguments ?? []); } - if (symbol?.name === "Map" && lowerer.isStdlibSymbol(symbol)) { + // A const alias (`const M = Map`) constructs the same Map. + if (ts.isIdentifier(identifier) && lowerer.isStdlibGlobal(identifier, "Map")) { const seedArg = (expr.arguments?.length ?? 0) === 1 ? expr.arguments![0]! : null; const isPairLit = (el: ts.Expression): el is ts.ArrayLiteralExpression => ts.isArrayLiteralExpression(el) && @@ -9315,7 +9276,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } // `new Set()`: construct + bulk add from supported values, // including live collection cursors. Duplicates collapse in insertion order. - if (symbol?.name === "Set" && lowerer.isStdlibSymbol(symbol)) { + if (ts.isIdentifier(identifier) && lowerer.isStdlibGlobal(identifier, "Set")) { const tsType = lowerer.typeOf(expr); let mapped = lowerer.mapTypeOf(tsType); const fieldType = assignedThisFieldType(lowerer, expr); diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 4ef139293..5f09342e8 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -11,6 +11,7 @@ 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 { lowerKeyObjectInstanceof } from "./builtins/key-object.js"; import { lowerNetAddressInstanceof, lowerNetAddressStaticRead } from "./builtins/net-address.js"; import { isExecExceptionType, refuseExecExceptionCode } from "./builtins/errors.js"; @@ -35,14 +36,28 @@ import { isZlibStreamMember, lowerZlibInstanceOf, lowerZlibStreamBytesWritten, - zlibClassAliasOf, - zlibClassModeOfExpr, - zlibClassName, } from "./builtins/compression.js"; +import { + builtinClassAliasOwnsNoSlot, + builtinClassRefOf, + sameBuiltinClass, +} from "./builtins/class-values.js"; import { constituentTypes } from "../ts7/checker.js"; import { dirname } from "node:path"; import * as posix from "node:path/posix"; import type { Lowerer } from "./lowerer.js"; +import { + inlineNamespaceRequireOf, + lowerWithInlineRequireInit, + wholeExportValue, +} from "./cjs-inline-require.js"; +import { tscInteropDefaultValueOf } from "./cjs-tsc-interop.js"; +import { + cjsExportMemberWriteGlobalOf, + cjsInFileExportGlobalOf, + isCjsExportsIdentifier, + lowerCjsExportsObjectValue, +} from "./cjs-exports-object.js"; import { checkedClassAssertion, checkedClassUnionAssertion } from "./class-assertions.js"; import { checkedArmExtraction } from "./coercions/unions.js"; import { narrowClassUnion, narrowStoredClassValue } from "./class-unions.js"; @@ -203,7 +218,9 @@ import { tryLowerExpression } from "./expressions/try-lower-expression.js"; import { fenceNodeModuleMutation, isNodeModuleValue, + lowerCjsTopLevelThis, lowerNodeModuleIdentifier, + isCjsModuleGlobal, lowerNodeModuleProperty, lowerRequireCacheElement, lowerRequireCacheHas, @@ -227,6 +244,7 @@ import { } from "./class-prototypes.js"; import { isClassCallback } from "./class-callbacks.js"; import { + builtinConstantsObject, builtinPrototypeMethod, lowerArrayIsArrayValue, lowerCheckedPredicateValue, @@ -441,6 +459,8 @@ 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. + refuseWebAssemblyUse(lowerer, expr); if (ts.isNumericLiteral(expr)) { const value = Number(expr.text.replace(/_/g, "")); @@ -606,6 +626,8 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { ); } if (ts.isTypeOfExpression(expr)) { + const webAssembly = lowerWebAssemblyTypeof(lowerer, expr.expression); + if (webAssembly) return webAssembly; if (lowerer.isStdlibGlobal(expr.expression, "process")) return { kind: "strLit", value: "object", type: STRING, loc }; if (lowerer.isStdlibGlobal(expr.expression, "fetch")) @@ -615,8 +637,9 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (isNodeModuleValue(lowerer, expr.expression)) { return { kind: "strLit", value: "object", type: STRING, loc }; } - // `typeof zlib.Gzip`, `typeof C` over a zlib class alias. - if (zlibClassModeOfExpr(lowerer, expr.expression) !== null) { + // `typeof EventEmitter`, `typeof zlib.Gzip`, `typeof C` over a + // builtin class alias. + if (builtinClassRefOf(lowerer, expr.expression) !== null) { return { kind: "strLit", value: "function", type: STRING, loc }; } if (ts.isPropertyAccessExpression(expr.expression)) { @@ -690,6 +713,30 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (!sym) { return { kind: "strLit", value: "undefined", type: STRING, loc }; } + // `const { inspect } = require("util")` / `import { inspect } from + // "node:util"`: a binding of a builtin module's exported function, + // class or object. JavaScript sources represent such a value without + // 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()) + ? lowerer.builtinImportOf(tested) + : null; + if ( + imported && + builtinModuleConstOf(lowerer, imported.module, imported.member) === undefined + ) { + const t = lowerer.typeOf(tested); + if ( + lowerer.checker.getCallSignatures(t).length > 0 || + lowerer.checker.getConstructSignatures(t).length > 0 + ) { + return { kind: "strLit", value: "function", type: STRING, loc }; + } + if ((t.flags & ts.TypeFlags.Object) !== 0) { + return { kind: "strLit", value: "object", type: STRING, loc }; + } + } if (lowerer.isStdlibSymbol(sym)) { const t = lowerer.typeOf(expr.expression); if ( @@ -833,6 +880,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const moduleValue = lowerNodeModuleIdentifier(lowerer, expr); if (moduleValue) return moduleValue; + if (expr.text === "exports") { + const exportsValue = lowerCjsExportsObjectValue(lowerer, expr); + if (exportsValue) return exportsValue; + } if (lowerer.isSelfReference(expr)) { return { kind: "selfRef", type: lowerer.ctx.selfType!, loc }; } @@ -877,13 +928,13 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { `a builtin function alias as an escaping value (call '${expr.text}' directly)`, ); } - // `const C = zlib.Gzip` owns no runtime slot: construction, + // `const E = EventEmitter` owns no runtime slot: construction, // instanceof, typeof and .name lower at their use sites. - if (sym && zlibClassAliasOf(lowerer, sym) !== null) { + if (sym && builtinClassAliasOwnsNoSlot(lowerer, sym)) { lowerer.unsupported( "SC1090", expr, - `a zlib class alias as an escaping value (use '${expr.text}' with new, instanceof, typeof or .name)`, + `a builtin class alias as an escaping value (use '${expr.text}' with new, instanceof, typeof or .name)`, ); } } @@ -1228,6 +1279,17 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (bi.module === "os" && bi.member === "constants") { return { kind: "libCall", fn: "os.constants", args: [], type: DYN, loc }; } + // `import { O_RDONLY } from "constants"`: the member of the frozen + // module object. + if (bi.module === "constants") { + return { + kind: "dynKeyGet", + value: builtinConstantsObject(lowerer, loc), + key: { kind: "strLit", value: bi.member, type: STRING, loc }, + type: DYN, + loc, + }; + } // tls.rootCertificates: a runtime-valued module constant (the // cached bundled-CA array) — the one member read that lowers // to a libCall instead of a baked literal. @@ -1270,6 +1332,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // their access sites only. The CommonJS namespace binding // (`const lib = require("./lib.js")`) gets the same fence. const builtinNamespace = lowerer.builtinNamespaceModuleOf(expr); + if (builtinNamespace === "constants") return builtinConstantsObject(lowerer, loc); if (builtinNamespace !== null) { const type: IrType = { kind: "moduleNs", moduleId: `builtin:${builtinNamespace}` }; return { kind: "moduleNsRef", moduleId: type.moduleId, type, loc }; @@ -1559,6 +1622,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { return { kind: "moduleNsRef", moduleId: moduleNs.moduleId, type: moduleNs, loc }; } const builtinNs = lowerer.builtinNamespaceModuleOf(expr); + if (builtinNs === "constants") return builtinConstantsObject(lowerer, loc); if (builtinNs !== null) { const type: IrType = { kind: "moduleNs", moduleId: `builtin:${builtinNs}` }; return { kind: "moduleNsRef", moduleId: type.moduleId, type, loc }; @@ -1661,6 +1725,9 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // constant. TypeScript keeps the fence (noImplicitThis makes it a // compile-time story there). if (isJsSourceFile(expr.getSourceFile())) { + // Module scope of a CommonJS file: `this` is module.exports. + const exportsThis = lowerCjsTopLevelThis(lowerer, expr); + if (exportsThis) return exportsThis; return { kind: "libCall", fn: "dyn.this", args: [], type: DYN, loc }; } lowerer.unsupported("SC1080", expr); @@ -1808,6 +1875,28 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (ts.isConditionalExpression(expr)) return lowerTernary(lowerer, expr); if (ts.isPropertyAccessExpression(expr)) { + // `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). + if (!expr.questionDotToken) { + const req = inlineNamespaceRequireOf(lowerer, expr.expression); + if (req !== null && !lowerer.inlineRequireInitDone.has(req.call)) { + return lowerWithInlineRequireInit(lowerer, req, () => lowerExpr(lowerer, expr)); + } + // `module.exports` compared by identity inside its own module, or + // refused by name as any other value (cjs-exports-object.ts). + if (expr.name.text === "exports") { + const exportsValue = lowerCjsExportsObjectValue(lowerer, expr); + if (exportsValue) return exportsValue; + } + // tsc's `x_1.default` over a dependency whose whole export is one + // value: that value (cjs-tsc-interop.ts). + const interop = tscInteropDefaultValueOf(lowerer, expr); + if (interop !== null) { + const value = wholeExportValue(lowerer, interop.dep, expr); + if (value !== null) return value; + } + } { const member = lifetimeRegionMemberOf(lowerer.program, expr, lowerer.experimentalFiles); if (member !== null) @@ -1821,10 +1910,11 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { const table = lowerInternTableProperty(lowerer, expr); if (table) return table; } - // `zlib.Gzip.name`, `C.name` over a zlib class alias. + // `EventEmitter.name`, `zlib.Gzip.name`, `C.name` over a builtin + // class alias. if (expr.name.text === "name" && !expr.questionDotToken) { - const mode = zlibClassModeOfExpr(lowerer, expr.expression); - if (mode !== null) return { kind: "strLit", value: zlibClassName(mode), type: STRING, loc }; + const ref = builtinClassRefOf(lowerer, expr.expression); + if (ref !== null) return { kind: "strLit", value: ref.name, type: STRING, loc }; } // Expando statics may still be absent even when JS inference sees // the eventual assignment's scalar type. Preserve the checked value. @@ -2153,35 +2243,12 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // global (pure alias plumbing) and fall through unchanged; write // positions belong to the export-assignment machinery. { - const sf = expr.getSourceFile(); - const cjsMemberRecv = - isModuleExportsAccess(expr.expression) || - (ts.isIdentifier(expr.expression) && - expr.expression.text === "exports" && - !lowerer.resolveLocal(expr.expression) && - !lowerer.globalOf(expr.expression)); const writePos = ts.isBinaryExpression(expr.parent) && expr.parent.left === expr && expr.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken; - if ( - !expr.questionDotToken && - cjsMemberRecv && - !writePos && - isCjsJsFile(sf, lowerer.program) - ) { - const sym = - lowerer.checker.getSymbolAtLocation(expr.name) ?? - lowerer.cjsModuleExportSymbol(sf, expr.name.text); - const resolved = - sym !== undefined && sym.flags & ts.SymbolFlags.Alias - ? lowerer.checker.getAliasedSymbol(sym) - : sym; - const g = - (sym ? lowerer.globalsBySymbol.get(sym) : undefined) ?? - (resolved ? lowerer.globalsBySymbol.get(resolved) : undefined); - if (g) return { kind: "varRef", localId: g.id, type: g.type, loc }; - } + const g = cjsInFileExportGlobalOf(lowerer, expr); + if (g && !writePos) return { kind: "varRef", localId: g.id, type: g.type, loc }; } // Namespace-qualified reads (`N.x`, `A.B.C.f`, import= alias // chains): the member is a compile-time-known declaration of a @@ -2777,6 +2844,23 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { ); } } + // A member a JavaScript module attached to one of its functions + // (`status.message = codes; module.exports = status`), read from + // TypeScript: JavaScript stores such members on the function's own + // property table (`fn.member = v` writes through the function's + // checked-dynamic face), so the read takes the same path and enters + // the static code at the type inference gave the member. + if (recvLowered.type.kind === "func" && jsFunctionExpandoMember(lowerer, expr)) { + const read: IrExpr = { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(recvLowered, DYN), + key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, + type: DYN, + loc, + }; + const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); + return mapped && mapped.kind !== "void" ? lowerer.coerceInto(expr, read, mapped) : read; + } lowerer.unsupported( "SC1090", expr, @@ -3161,51 +3245,6 @@ export function lowerTernary( * the same union (unnarrowed use), to a SUB-union (partial narrowing — * unrepresentable without a re-tag), or to nothing (`never` in an * exhaustive default) leaves the expression union-typed. */ -/** Uses that read an early-read field's value as Node does when it is - * undefined: printing, templates, operators, tests, member access, calls of - * program functions (whose parameters the optional-value analysis widens), - * and stores into bindings, fields and returns. Other uses, such as library - * arguments, extract the declared type with a check. */ -function observesEarlyRead(node: ts.Node): boolean { - let child = node; - let parent = node.parent; - while (parent && ts.isParenthesizedExpression(parent)) { - child = parent; - parent = parent.parent; - } - if (!parent) return false; - if ( - ts.isTemplateSpan(parent) || - ts.isBinaryExpression(parent) || - ts.isPrefixUnaryExpression(parent) || - ts.isPostfixUnaryExpression(parent) || - ts.isTypeOfExpression(parent) || - ts.isConditionalExpression(parent) || - ts.isIfStatement(parent) || - ts.isWhileStatement(parent) || - ts.isReturnStatement(parent) || - ts.isVariableDeclaration(parent) || - ts.isPropertyAssignment(parent) || - ts.isArrayLiteralExpression(parent) || - (ts.isPropertyAccessExpression(parent) && parent.expression === child) || - (ts.isElementAccessExpression(parent) && parent.expression === child) - ) - return true; - if (ts.isCallExpression(parent) && parent.arguments.includes(child as ts.Expression)) { - const callee = parent.expression; - if ( - ts.isPropertyAccessExpression(callee) && - ts.isIdentifier(callee.expression) && - callee.expression.text === "console" - ) - return true; - return ( - !ts.isPropertyAccessExpression(callee) || callee.expression.kind === ts.SyntaxKind.ThisKeyword - ); - } - return false; -} - export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrExpr { if ( (expr.type.kind === "union" || expr.type.kind === "dyn") && @@ -3416,25 +3455,6 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx if (expr.type.kind !== "union") return expr; if (ts.isPropertyAccessExpression(node) && isOptionalProcessStreamProperty(lowerer, node)) return expr; - // A field read before its assignment holds undefined although its - // declared type omits it; the declared type is no narrowing proof. A flow - // narrowing past the declared type (`if (this.x !== null)`) extracts the - // narrowed arm with a check, since the slot may still hold undefined. - if ( - expr.kind === "fieldGet" && - lowerer.classes.get(expr.className)?.earlyReadFields?.has(expr.field) === true - ) { - const symbol = ts.isPropertyAccessExpression(node) - ? lowerer.checker.getSymbolAtLocation(node.name) - : undefined; - const declared = symbol ? lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(symbol)) : null; - const narrowedArm = lowerer.mapTypeOf(lowerer.typeOf(node)); - if (narrowedArm === null || narrowedArm.kind === "union") return expr; - if (declared !== null && typeEquals(declared, narrowedArm) && observesEarlyRead(node)) - return expr; - const helper = lowerer.narrowedArmHelper(expr.type.unionId, narrowedArm, expr.loc); - return helper ? lowerer.checkedNarrowCall(helper, expr, narrowedArm, expr.loc) : expr; - } // Represented fields and index values can include undefined even when // checker inference or lib.d.ts declares strings (RegExp captures). @@ -5082,6 +5102,34 @@ export function lowerIntrinsicProperty( return { kind: "libCall", fn: "dyn.arrayPrototype", args: [], type: DYN, loc: locOf(expr) }; if (lowerer.stdlibGlobalMember(expr, "Object") === "prototype") return { kind: "libCall", fn: "dyn.objectPrototype", args: [], type: DYN, loc: locOf(expr) }; + // Function.prototype as a VALUE (a prototype-chain link: + // `Object.setPrototypeOf(F.prototype, Function.prototype)`): one + // anonymous function answering undefined, length 0 — a zero-capture + // closure of a compiler helper, so every read is the same function + // (closure identity) and shares its property table. Reads through it + // find the function members the checked runtime models for every + // function (apply, constructor, name, length). Member forms keep their + // own lowerings. + if (lowerer.stdlibGlobalMember(expr, "Function") === "prototype") { + const loc = locOf(expr); + const name = "%dyn.functionPrototype"; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) + lowerer.liftedFns.push({ + name, + params: [], + locals: [], + returnType: DYN, + body: [{ kind: "return", value: dynUndefinedExpr(loc), loc }], + loc, + }); + return { + kind: "dynFrom", + value: { kind: "closure", fnName: name, captures: [], type: funcOf([], DYN), loc }, + fnName: "", + type: DYN, + loc, + }; + } if (lowerer.stdlibGlobalMember(expr, "Array") === "isArray") return lowerArrayIsArrayValue(lowerer, locOf(expr)); if (lowerer.stdlibGlobalMember(expr, "Array") === "from") @@ -10159,6 +10207,35 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr // RHS value is the expression's value — its OWN type, so the // consumer sees exactly what the checker typed. if (ts.isPropertyAccessExpression(expr.left) && !expr.left.questionDotToken) { + // `const src = exports.src = []`: an export member assigned in + // value position writes its export global and yields the value. + { + let inFunction = false; + for (let p: ts.Node | undefined = expr.parent; p !== undefined; p = p.parent) { + if (ts.isFunctionLike(p) || ts.isClassDeclaration(p) || ts.isClassExpression(p)) { + inFunction = true; + break; + } + } + const g = inFunction + ? cjsExportMemberWriteGlobalOf(lowerer, expr.left) + : cjsInFileExportGlobalOf(lowerer, expr.left); + if (g) { + const value = lowerer.lowerExprExpecting(expr.right, g.type); + const tmp = lowerer.declareHiddenLocal("%exportValue", value.type); + const result = varRef(tmp.id, value.type, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: tmp.id, init: value, loc }, + { kind: "assign", localId: g.id, value: result, loc }, + ], + result, + type: value.type, + loc, + }; + } + } const recv = tryLowerExpression(lowerer, expr.left.expression); if (recv && recv.type.kind === "dyn") return lowerDynMemberAssignment(lowerer, expr, recv); const unionWrite = lowerUnionFieldWrite(lowerer, expr.left, expr.right); @@ -10268,23 +10345,25 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr op === ts.SyntaxKind.ExclamationEqualsEqualsToken ) { const test = + lowerWebAssemblyTypeofTest(lowerer, expr, loc) ?? lowerErrorCodeTypeofTest(lowerer, expr, loc) ?? lowerer.lowerCaughtTypeofTest(expr, loc) ?? lowerDynTypeofTest(lowerer, expr, loc) ?? lowerUnionTypeofTest(lowerer, expr, loc); if (test) return test; } - // `C === zlib.Gzip` between two zlib class references: the classes - // are compile-time values, so the answer folds. + // `C === EventEmitter` between two builtin class references: the + // classes are compile-time values, so the answer folds. if ( op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsEqualsToken ) { - const leftClass = zlibClassModeOfExpr(lowerer, expr.left); - const rightClass = leftClass === null ? null : zlibClassModeOfExpr(lowerer, expr.right); + const leftClass = builtinClassRefOf(lowerer, expr.left); + const rightClass = leftClass === null ? null : builtinClassRefOf(lowerer, expr.right); if (leftClass !== null && rightClass !== null) { const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken; - return { kind: "boolLit", value: (leftClass === rightClass) !== negated, type: BOOL, loc }; + const same = sameBuiltinClass(leftClass, rightClass); + return { kind: "boolLit", value: same !== negated, type: BOOL, loc }; } } @@ -11538,6 +11617,34 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE // same lift the union-operand path above takes, arriving here // with two plain arm values instead. const target = lowerer.mapTypeOf(lowerer.typeOf(expr)); + // An always-truthy left operand (a record, object, array or function + // value: every JS object is truthy): `o && x` evaluates `o` for its + // effects and answers `x`; `o || x` answers `o` and never evaluates + // `x`. supports-color's `stream && stream.isTTY` is the shape. Ahead + // of the union lift below exactly where that lift would store the + // object in a result union with no arm for it; a union holding the + // operand keeps the lift. + if ( + REF_TRUTHY_KINDS.has(kind) && + (target?.kind !== "union" || + (op === ts.SyntaxKind.AmpersandAmpersandToken && + lowerer.armTag(target.unionId, left.type) < 0)) + ) { + const and = op === ts.SyntaxKind.AmpersandAmpersandToken; + const answer = and ? right : left; + const answerT = target ?? answer.type; + if (answerT.kind !== "void") { + const value = lowerer.coerceInto(and ? expr.right : expr.left, answer, answerT); + if (!and) return value; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: left, loc }], + result: value, + type: answerT, + loc, + }; + } + } if (target?.kind === "union") { lowerer.requireTruthyUnion(target.unionId, expr); return { @@ -11631,6 +11738,24 @@ function errorRootedObjectOf(lowerer: Lowerer, t: ts.Type): IrType | null { return null; } +/** `typeof WebAssembly === "object"` and `typeof WebAssembly. === + * "function"`: the operand needs no value, so the test folds as bare typeof + * does (builtins/webassembly.ts) before any dyn-typed operand lowers. */ +function lowerWebAssemblyTypeofTest( + lowerer: Lowerer, + expr: ts.BinaryExpression, + loc: SrcLoc, +): IrExpr | null { + const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken; + for (const [a, b] of operandPairs(expr)) { + if (!ts.isTypeOfExpression(a) || !ts.isStringLiteral(b)) continue; + const folded = lowerWebAssemblyTypeof(lowerer, a.expression); + if (folded?.kind !== "strLit") continue; + return { kind: "boolLit", value: (folded.value === b.text) !== negated, type: BOOL, loc }; + } + return null; +} + /** `typeof e.code === "string"` / `"undefined"` (and the `!==` forms) * where e — through parens and as-casts — is a catch binding or a value * of %Error-rooted type: the runtime error's code slot is a string @@ -12328,15 +12453,16 @@ 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 { + 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. const sockTest = lowerSocketInstanceOf(lowerer, expr, loc); if (sockTest !== null) return sockTest; - const zlibMode = zlibClassModeOfExpr(lowerer, expr.right); - if (zlibMode !== null) return lowerZlibInstanceOf(lowerer, expr, zlibMode, loc); + const rightClass = builtinClassRefOf(lowerer, expr.right); + if (rightClass?.kind === "zlib") return lowerZlibInstanceOf(lowerer, expr, rightClass.mode, loc); - if (lowerer.isStdlibGlobal(expr.right, "Set") || lowerer.isStdlibGlobal(expr.right, "Map")) { - const collection = lowerer.isStdlibGlobal(expr.right, "Map") ? "map" : "set"; + if (rightClass?.kind === "collection") { + const collection = rightClass.which === "Map" ? "map" : "set"; const value = lowerer.lowerExpr(expr.left); if (value.type.kind === "union") { const union = lowerer.unions.get(value.type.unionId); @@ -12684,42 +12810,68 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc "'instanceof' right-hand sides other than classes declared in the program", ); } + // `x instanceof Writable`: Node's Writable[Symbol.hasInstance] also + // answers true for streams with a writable side that do not inherit from + // Writable, which are Duplex and its subclasses (Transform, PassThrough, + // the zlib streams). Subclasses of Writable keep the prototype test. + const duplex = + target.def.name === "%Writable" && target.builtinStream !== undefined + ? lowerer.classes.get("%Duplex") + : undefined; + // The classes whose interval test can answer true for a value of static + // class `className`: Writable and Duplex are disjoint, so a value typed + // as one side needs only that side's test. + const runtimeTests = (className: string): ClassInfo[] => { + if (!duplex) return [target]; + const mayHold = (info: ClassInfo): boolean => + className === info.def.name || + lowerer.isSubclassOf(className, info.def.name) || + lowerer.isSubclassOf(info.def.name, className); + const tests = [target, duplex].filter(mayHold); + return tests.length > 0 ? tests : [target]; + }; // A catch binding on the left: the runtime test against the class's // preorder interval (false for non-hierarchy-object payloads). tsc's // narrowing then types the branches; reads bridge through caughtRead. const caughtLocal = lowerer.caughtLocalOf(expr.left); if (caughtLocal) { - if (!lowerer.inHierarchy(target)) { - const value = varRef(caughtLocal.id, CAUGHT, loc); - return { - kind: "ternary", - cond: { - kind: "caughtTest", - value, - test: "instanceof", - className: target.def.name, + const caughtInstanceOf = (info: ClassInfo): IrExpr => { + if (!lowerer.inHierarchy(info)) { + const value = varRef(caughtLocal.id, CAUGHT, loc); + return { + kind: "ternary", + cond: { + kind: "caughtTest", + value, + test: "instanceof", + className: info.def.name, + type: BOOL, + loc, + }, + then: { kind: "boolLit", value: true, type: BOOL, loc }, + else_: classInstanceOf( + lowerer, + { kind: "caughtToDyn", value, type: DYN, loc }, + info, + loc, + ), type: BOOL, loc, - }, - then: { kind: "boolLit", value: true, type: BOOL, loc }, - else_: classInstanceOf( - lowerer, - { kind: "caughtToDyn", value, type: DYN, loc }, - target, - loc, - ), + }; + } + return { + kind: "caughtTest", + value: { kind: "varRef", localId: caughtLocal.id, type: CAUGHT, loc }, + test: "instanceof", + className: info.def.name, type: BOOL, loc, }; - } - return { - kind: "caughtTest", - value: { kind: "varRef", localId: caughtLocal.id, type: CAUGHT, loc }, - test: "instanceof", - className: target.def.name, - type: BOOL, - loc, }; + const test = caughtInstanceOf(target); + return duplex + ? { kind: "logical", op: "||", left: test, right: caughtInstanceOf(duplex), type: BOOL, loc } + : test; } const left = lowerer.lowerExpr(expr.left); // `u instanceof Error` on an `unknown` value: the checked-dynamic tree's error encoding @@ -12799,23 +12951,33 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc const lhsInfo = lowerer.classes.get(arm.className); if (!lhsInfo) throw new InternalCompilerError(`lowerer bug: unknown class ${arm.className}`); - const result: IrExpr = - lowerer.inHierarchy(lhsInfo) && lowerer.inHierarchy(target) + const armTest = (info: ClassInfo): IrExpr => + lowerer.inHierarchy(lhsInfo) && lowerer.inHierarchy(info) ? { kind: "instanceOf", value: { kind: "unionNarrow", unionId, tag, value, type: arm, loc }, - className: target.def.name, + className: info.def.name, type: BOOL, loc, } : { kind: "boolLit", value: - arm.className === target.def.name || - lowerer.isSubclassOf(arm.className, target.def.name), + arm.className === info.def.name || + lowerer.isSubclassOf(arm.className, info.def.name), type: BOOL, loc, }; + const result = runtimeTests(arm.className) + .map(armTest) + .reduce((left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "logical", + op: "||", + left, + right, + type: BOOL, + loc, + })); body.push({ kind: "if", cond: { kind: "unionIsTag", unionId, tag, negated: false, value, type: BOOL, loc }, @@ -12861,11 +13023,39 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc if (!lhsInfo) throw new InternalCompilerError(`lowerer bug: unknown class ${left.type.className}`); if (lowerer.inHierarchy(lhsInfo) && lowerer.inHierarchy(target)) { - return { kind: "instanceOf", value: left, className: target.def.name, type: BOOL, loc }; + const tests = runtimeTests(left.type.className); + if (tests.length === 1) { + return { kind: "instanceOf", value: left, className: tests[0]!.def.name, type: BOOL, loc }; + } + const local = lowerer.declareHiddenLocal("%writableTest", left.type); + const ref = varRef(local.id, left.type, loc); + const test = (className: string): IrExpr => ({ + kind: "instanceOf", + value: ref, + className, + type: BOOL, + loc, + }); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: left, loc }], + result: { + kind: "logical", + op: "||", + left: test(tests[0]!.def.name), + right: test(tests[1]!.def.name), + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; } + const leftClass = left.type.className; + const isInstance = (className: string): boolean => + leftClass === className || lowerer.isSubclassOf(leftClass, className); const value = - left.type.className === target.def.name || - lowerer.isSubclassOf(left.type.className, target.def.name); + isInstance(target.def.name) || (duplex !== undefined && isInstance(duplex.def.name)); if (left.kind === "varRef") { return readsClassAfter(lowerer, target, expr.right, [], { kind: "boolLit", @@ -14957,6 +15147,9 @@ export function fieldTarget( access: ts.PropertyAccessExpression, ): FieldTarget | null { if (lowerer.cjsLocalModuleBindingOf(access.expression)) return null; + // `module.exports` / `module.id`: CommonJS module surface, never a field. + if (isCjsModuleGlobal(lowerer, access.expression)) return null; + if (isCjsExportsIdentifier(lowerer, access.expression)) return null; if (lowerer.chainBlocked(access)) return null; if ( access.name.text === "stackTraceLimit" && @@ -15465,20 +15658,6 @@ export function fieldGetExpr( const helper = lowerer.deferredReadHelper(target.fieldType.unionId, inner, loc); if (helper) return { kind: "call", callee: helper, args: [read], type: inner, loc }; } - // An early-read field (ClassInfo.earlyReadFields) yields its undefined - // where the use reads it as Node does; elsewhere the declared type is - // extracted with a check. - if ( - target.fieldType.kind === "union" && - lowerer.classes.get(target.className)?.earlyReadFields?.has(target.field) === true && - !observesEarlyRead(blame) - ) { - const declared = lowerer.mapTypeOf(lowerer.typeOf(blame)); - if (declared && declared.kind !== "union") { - const helper = lowerer.narrowedArmHelper(target.fieldType.unionId, declared, loc); - if (helper) return lowerer.checkedNarrowCall(helper, read, declared, loc); - } - } return read; } return { @@ -16019,22 +16198,6 @@ function lowerFieldCompoundValue( if (op === "+" && read.type.kind === "string") { appended = lowerer.ensureString(rhs, rhsNode ?? access); value = { kind: "strConcat", left: read, right: appended, type: STRING, loc }; - } else if ( - op === "+" && - read.type.kind === "union" && - lowerer.stripUndefinedArm(read.type).kind === "string" && - target.container === "class" && - lowerer.classes.get(target.className)?.earlyReadFields?.has(target.field) === true - ) { - // A string field read before its assignment holds undefined, which - // concatenates as "undefined". - value = { - kind: "strConcat", - left: lowerer.ensureString(read, access), - right: lowerer.ensureString(rhs, rhsNode ?? access), - type: STRING, - loc, - }; } else if (numericRead.type.kind === "f64" && numericRhs.type.kind === "f64") { value = { kind: "bin", op, left: numericRead, right: numericRhs, type: F64, loc }; } else if (bigintValue) { @@ -16273,3 +16436,28 @@ function isBuiltinModuleValueDecl(decl: ts.Node): boolean { ts.isVariableDeclaration(decl)) ); } + +/** True when `access` reads a member that JavaScript sources attach to a + * function by assignment (`fn.member = value`): the receiver's type is + * callable and every declaration of the member is such an assignment in a + * JS file. */ +function jsFunctionExpandoMember(lowerer: Lowerer, access: ts.PropertyAccessExpression): boolean { + if (access.questionDotToken || !ts.isIdentifier(access.name)) return false; + const recvType = lowerer.typeOf(access.expression); + if (lowerer.checker.getCallSignatures(recvType).length === 0) return false; + const member = lowerer.checker.getPropertyOfType(recvType, access.name.text); + const decls = member ? lowerer.checker.declarationsOf(member) : []; + return ( + decls.length > 0 && + decls.every((d) => { + if (!isJsSourceFile(d.getSourceFile())) return false; + const assign = ts.isBinaryExpression(d) ? d : d.parent; + return ( + assign !== undefined && + ts.isBinaryExpression(assign) && + assign.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(assign.left) + ); + }) + ); +} diff --git a/packages/compiler/src/frontend/lowering/lower-generators.ts b/packages/compiler/src/frontend/lowering/lower-generators.ts index 2eda4c4ce..a9b467a6a 100644 --- a/packages/compiler/src/frontend/lowering/lower-generators.ts +++ b/packages/compiler/src/frontend/lowering/lower-generators.ts @@ -115,6 +115,16 @@ export function lowerGenMethodCall( if (call.arguments.some(ts.isSpreadElement)) { lowerer.unsupported("SC1090", call, "spread arguments"); } + // Iterator helpers and the built-in iterators have no throw method; an + // iterator-typed value may hold one, so only a generator's own member + // lowers. + if (name === "throw" && !isGeneratorMember(lowerer, access)) { + lowerer.noLowering( + `${lowerer.checker.typeToString(lowerer.typeOf(access.expression))}.throw`, + call, + "throw() lowers on values typed as generators (iterator helpers and built-in iterators have no throw method)", + ); + } const loc = locOf(call); const gen = lowerer.lowerExpr(access.expression); if (gen.type.kind !== "generator") return null; @@ -187,6 +197,23 @@ export function lowerGenMethodCall( return { kind: "genResume", mode: name, gen, arg, type: resultT, loc }; } +/** Whether `access` names a member the lib declares on Generator or + * AsyncGenerator itself. */ +function isGeneratorMember(lowerer: Lowerer, access: ts.PropertyAccessExpression): boolean { + const member = lowerer.checker.getSymbolAtLocation(access.name); + return ( + member !== undefined && + lowerer.checker + .declarationsOf(member) + .some( + (d) => + ts.isInterfaceDeclaration(d.parent) && + (d.parent.name.text === "Generator" || d.parent.name.text === "AsyncGenerator") && + lowerer.isStdlibFile(d.getSourceFile()), + ) + ); +} + /** Extraction of a suspended resume's yield value out of the result * record's V slot, typed back to the yield channel: V IS the channel for * dyn (and for a union channel already equal to V); a non-union channel @@ -319,7 +346,7 @@ export function genValue(gen: IrExpr & { type: GenType }, loc: SrcLoc): IrExpr { return { kind: "genResume", mode: "value", gen, arg: null, type: gen.type.yieldT, loc }; } -function generatorLoopBinding( +export function generatorLoopBinding( lowerer: Lowerer, stmt: ts.ForOfStatement, value: IrExpr, @@ -572,7 +599,9 @@ export function lowerForOfGenerator( catchBody: null, catchLocalId: null, // IteratorClose: every exit that leaves the generator unfinished - // closes it — finallys run; the .return() result is dropped. + // closes it — finallys run; the .return() result is dropped, and + // after a throw from the body an error from the close is too. + dropFinallyErrors: true, finallyBody: [ { kind: "if", diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index c5802b07e..b375668bc 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -18,7 +18,9 @@ import { isRelativeSpecifier } from "../workspace-registry.js"; import { canonicalBuiltinModule, cjsExportAssignmentOf, + cjsExportChainOf, cjsExportDiscardReason, + cjsExportTargetLiteral, entryPackageFilePredicate, isCjsJsFile, isJsSourceFile, @@ -26,6 +28,7 @@ import { locOf, makeCycleAdmission, orderedImportsOf, + requireSpecOf, resolveImport, resolveNpmImport, } from "../program.js"; @@ -36,6 +39,13 @@ import { withCycleInitFlags, } from "./cycle-initialization.js"; import { markRequireEarlyBindings } from "./require-initialization.js"; +import { + cjsTableMethodEntry, + inlineProgramRequireOf, + nestedCjsExportAssignmentsOf, + requireNestedPatternsOf, +} from "./cjs-inline-require.js"; +import { isDeadTscHelperDecl, tscInteropBindingOfDecl } from "./cjs-tsc-interop.js"; import { collectJsonRequires, isJsonRequireDecl } from "./json-require.js"; import { invalidJsonModuleDiag, @@ -80,7 +90,7 @@ import { createRequireProgramModuleDecl, } from "./builtins/module-bindings.js"; import { isPromisifyCall, registerBuiltinCallableAlias } from "./builtins/callable-aliases.js"; -import { registerZlibClassAlias } from "./builtins/compression.js"; +import { registerBuiltinClassAlias } from "./builtins/class-values.js"; import { stripTypeCasts } from "./builtins/arguments.js"; import { bindingContextualGenericFnNodeOf, @@ -1391,6 +1401,11 @@ export function collectGlobals( deferJsCollectionDiags(lowerer, sf, diagsBefore); } }; + // Export members assigned in VALUE position at the module's top + // level (`const src = exports.src = []`) are exports too. + for (const nested of nestedCjsExportAssignmentsOf(stmt, cjs?.expr)) { + registerExport(nested.name, nested.name.text, nested.value); + } if (cjs?.kind === "member" && ts.isIdentifier(cjs.name)) { // `exports.C = C` naming a CLASS declaration: alias plumbing — // no storage (a class VALUE global would fence for @@ -1398,6 +1413,13 @@ export function collectGlobals( // symbol (cjsMemberExportClassSymbol) and the class registry // applies unchanged. if (lowerer.cjsMemberExportClassSymbol(cjs.expr) !== null) continue; + // A chain `exports.a = exports.b = value`: every member is an + // export holding the chain's one value. + const chain = cjsExportChainOf(cjs.expr); + if (chain !== null) { + for (const nameNode of chain.names) registerExport(nameNode, nameNode.text, chain.value); + continue; + } registerExport(cjs.name, cjs.name.text, cjs.value); continue; } @@ -1429,7 +1451,22 @@ export function collectGlobals( // statement lowering evaluates the call exactly once at its // source position and assigns that slot. Unmappable calls retain // the existing statement-level fence. - const factoryValued = ts.isCallExpression(rhs); + // Any other computed value (`os.homedir || function () {…}`, + // `cond ? a : b`, `new Registry()`, `ns.member`) is the same single + // snapshot: Node stores whatever the expression produced. A + // require() of a program module is not: the exporter re-exports + // that module's exports, which requirers resolve through the + // checker's alias (the statement lowers to the dep's %init). + const factoryValued = + !ts.isIdentifier(rhs) && + !ts.isClassExpression(rhs) && + !ts.isObjectLiteralExpression(rhs) && + !( + requireSpecOf(rhs) !== null && + inlineProgramRequireOf(lowerer, rhs) !== null && + !sf.statements.some((s) => cjsExportAssignmentOf(s)?.kind === "member") + ) && + cjsExportTargetLiteral(rhs, sf) === null; if (cjsScalarLiteral(rhs) || fnValued || factoryValued) { const diagsBefore = lowerer.diags.length; try { @@ -1464,6 +1501,10 @@ export function collectGlobals( if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) { registerExport(prop.name, prop.name.text, prop.initializer); } + // A METHOD entry (`module.exports = { f(x) { … } }`) is an + // expression-valued export whose value is the method's function. + const method = cjsTableMethodEntry(prop); + if (method !== null) registerExport(method.name, method.name.text, method.name); // other property forms fence in the statement lowering } continue; @@ -1527,6 +1568,16 @@ export function collectGlobals( collectNestedVarGlobals(lowerer, sf, stmt, tag); continue; } + // tsc's interop bindings and its then-dead helper declarations own no + // storage either (cjs-tsc-interop.ts). + if ( + isJsSourceFile(sf) && + (isDeadTscHelperDecl(lowerer, stmt) || + (stmt.declarationList.declarations.length === 1 && + tscInteropBindingOfDecl(lowerer, stmt.declarationList.declarations[0]!) !== null)) + ) { + continue; + } // CommonJS require declarations are alias plumbing (preflight owns // the module edges; resolveValueSymbol routes reads through to the // exporter's declarations): no storage, and the statement lowers to @@ -1549,6 +1600,38 @@ export function collectGlobals( stdlibGlobalAliasDecl(lowerer, decl.name, decl.initializer); } } + // `const { codes: { ERR_X } } = require("./errors")`: the outer + // keys stay alias plumbing, but a nested pattern destructures the + // export's value at the require, so its bindings own storage like + // any destructured declaration (lowerVarDeclarator assigns them). + if (ts.isVariableStatement(stmt)) { + const isLetList = + (stmt.declarationList.flags & ts.NodeFlags.Let) !== 0 || + (stmt.declarationList.flags & ts.NodeFlags.BlockScoped) === 0; + for (const decl of stmt.declarationList.declarations) { + for (const element of requireNestedPatternsOf(decl)) { + for (const nameNode of boundIdentifiersOf(element.name!)) { + const symbol = lowerer.checker.getSymbolAtLocation(nameNode); + if (!symbol || lowerer.globalsBySymbol.has(symbol)) continue; + const mapped = lowerer.mapTypeOf(lowerer.typeOf(nameNode)); + const type = mapped && mapped.kind !== "void" ? mapped : DYN; + const g: IrGlobal = { + id: `%g.${tag}${nameNode.text}`, + name: nameNode.text, + type, + mutable: isLetList, + source: bindingSource(nameNode), + ...((type.kind === "func" || type.kind === "dyn") && !isVarDeclared(decl) + ? { tdz: true as const } + : {}), + }; + lowerer.globalsBySymbol.set(symbol, g); + lowerer.globalsList.push(g); + if (isVarDeclared(decl)) noteVarGlobalEntryInit(lowerer, sf, g); + } + } + } + } continue; } // `declare const __VERSION__: string` — AMBIENT: no storage exists @@ -1701,7 +1784,7 @@ export function collectGlobals( // resolution and value reads reuse the source's interned closure. if (isConst && registerOverloadedCallableAlias(lowerer, decl)) continue; if (isConst && registerBuiltinCallableAlias(lowerer, decl.name, decl.initializer)) continue; - if (registerZlibClassAlias(lowerer, decl.name, decl.initializer)) continue; + if (registerBuiltinClassAlias(lowerer, decl.name, decl.initializer)) continue; // `const f = (x) => ...` at file scope of an npm-static JS file // whose params are implicit-any: the implicit-monomorphization // twin of the generic binding above — no global exists; calls and @@ -2664,6 +2747,18 @@ export function lowerFileInit( loc: loc0, }); } + if (sf === lowerer.entry) header.push(...lowerer.punycodeLinkWarning()); + // Top-level `this` of a CommonJS module: its object exists before the + // module's first statement (lowerCjsTopLevelThis). + const exportsThis = lowerer.cjsExportsThis.get(sf); + if (exportsThis) { + header.push({ + kind: "assign", + localId: exportsThis.id, + value: { kind: "dynObjLit", fields: [], type: DYN, loc: loc0 }, + loc: loc0, + }); + } for (const { stmt, dep } of orderedImportsOf(lowerer.program, sf)) { const npm = lowerer.npmInitActions.get(sf)?.get(stmt); if (npm !== undefined && npm.length > 0) { diff --git a/packages/compiler/src/frontend/lowering/lower-node-module.ts b/packages/compiler/src/frontend/lowering/lower-node-module.ts index ad3f86dee..292b38e87 100644 --- a/packages/compiler/src/frontend/lowering/lower-node-module.ts +++ b/packages/compiler/src/frontend/lowering/lower-node-module.ts @@ -8,10 +8,12 @@ import * as win32 from "node:path/win32"; import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; import type { FileParts } from "./lower-modules.js"; -import { isNodeEsmFile, locOf } from "../program.js"; +import { lowerCjsExportsObjectValue } from "./cjs-exports-object.js"; +import { isCjsJsFile, isNodeEsmFile, locOf } from "../program.js"; import { runtimePathForTarget } from "../runtime-resolve.js"; import { BOOL, + DYN, F64, type IrExpr, type IrStmt, @@ -176,6 +178,12 @@ export function cjsModuleRef(lowerer: Lowerer, node: ts.Node): IrExpr | null { return id === undefined ? null : numLit(id, locOf(node)); } +/** True when `node` is the CommonJS `require` global (not shadowed by a + * source binding, and not in an ES module). */ +export function isCjsRequireGlobal(lowerer: Lowerer, node: ts.Expression): boolean { + return cjsGlobal(lowerer, node, "require"); +} + export function isCjsModuleGlobal(lowerer: Lowerer, node: ts.Expression): boolean { return cjsGlobal(lowerer, node, "module"); } @@ -224,6 +232,135 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { return rendered === "ScriptcModule" || rendered === "NodeModule" || rendered === "NodeJS.Module"; } +/** `module.exports` used only for its truthiness (the UMD test `typeof + * module !== "undefined" && module.exports`): the exports object starts as + * a fresh object, so it stays truthy unless the module replaces it with a + * value that can be falsy. True when every `module.exports = value` in the + * file assigns an object or function. */ +function moduleExportsAlwaysTruthy(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { + if (!truthinessOnly(expr)) return false; + const sf = expr.getSourceFile(); + if (isNodeEsmFile(sf, lowerer.program)) return false; + let truthy = true; + ts.walkPreorder(sf, (node) => { + if ( + !ts.isBinaryExpression(node) || + node.operatorToken.kind < ts.SyntaxKind.FirstAssignment || + node.operatorToken.kind > ts.SyntaxKind.LastAssignment + ) + return undefined; + let target = node.left; + while (ts.isParenthesizedExpression(target)) target = target.expression; + if ( + !ts.isPropertyAccessExpression(target) || + target.name.text !== "exports" || + !isCjsModuleGlobal(lowerer, target.expression) + ) + return undefined; + const type = lowerer.typeOf(node.right); + const objectLike = (t: ts.Type): boolean => + t.isUnionType() + ? ts.constituentTypes(t).every(objectLike) + : (t.flags & ts.TypeFlags.Object) !== 0 || (t.flags & ts.TypeFlags.NonPrimitive) !== 0; + if (node.operatorToken.kind !== ts.SyntaxKind.EqualsToken || !objectLike(type)) { + truthy = false; + return "stop"; + } + return undefined; + }); + return truthy; +} + +/** Whether only the truthiness of `node` can be observed: a condition, a + * `!` operand, or a `&&`/`||` operand of an expression that is itself used + * only for its truthiness. */ +function truthinessOnly(node: ts.Expression): boolean { + let child: ts.Node = node; + for (let parent = node.parent; ; child = parent, parent = parent.parent) { + if (ts.isParenthesizedExpression(parent)) continue; + if (ts.isPrefixUnaryExpression(parent)) + return parent.operator === ts.SyntaxKind.ExclamationToken; + if ( + ts.isBinaryExpression(parent) && + (parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || + parent.operatorToken.kind === ts.SyntaxKind.BarBarToken) + ) + continue; + 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; + return false; + } +} + +/** `this` at the top level of a CommonJS module (directly, or inside + * arrow functions there): Node calls the module wrapper with `this` bound + * to the module's original exports object. */ +export function isCjsTopLevelThis(lowerer: Lowerer, node: ts.Node): boolean { + if (node.kind !== ts.SyntaxKind.ThisKeyword) return false; + for (let p: ts.Node | undefined = node.parent; p && !ts.isSourceFile(p); p = p.parent) { + if (ts.isArrowFunction(p)) continue; + if ( + ts.isFunctionLike(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p) || + ts.isClassStaticBlockDeclaration(p) + ) + return false; + } + return isCjsJsFile(node.getSourceFile(), lowerer.program); +} + +export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolean { + if (!isCjsJsFile(sf, lowerer.program)) return false; + let found = false; + ts.walkPreorder(sf, (node) => { + if ( + node !== sf && + !ts.isArrowFunction(node) && + (ts.isFunctionLike(node) || + ts.isClassDeclaration(node) || + ts.isClassExpression(node) || + ts.isClassStaticBlockDeclaration(node)) + ) + return "skip"; + if (node.kind === ts.SyntaxKind.ThisKeyword) { + found = true; + return "stop"; + } + return undefined; + }); + return found; +} + +/** The value of top-level `this` in a CommonJS module: the module's own + * object standing for its original exports, created when the module starts + * (lowerFileInit). Exports themselves are the static export table, so a + * property access through `this` would read or write an object no requirer + * sees; it keeps a named refusal instead. Typeof, truthiness, identity and + * passing the value along (the UMD wrapper `(function (root) { ... })(this)`) + * 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()); + if (!global) return null; + let receiver: ts.Node = expr; + while (ts.isParenthesizedExpression(receiver.parent)) receiver = receiver.parent; + const access = receiver.parent; + if ( + (ts.isPropertyAccessExpression(access) || ts.isElementAccessExpression(access)) && + access.expression === receiver + ) { + lowerer.unsupported( + "SC1090", + access, + "property access through top-level 'this' in a CommonJS module (it is module.exports; spell exports.)", + ); + } + return varRef(global.id, DYN, locOf(expr)); +} + function optionalModule( lowerer: Lowerer, node: ts.Expression, @@ -298,6 +435,13 @@ 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)) { + lowerer.unsupported( + "SC1090", + expr, + "the CommonJS 'module' object in this position (module.exports members, the whole module.exports in comparisons, and the module metadata fields lower)", + ); + } lowerer.unsupported("SC1090", expr, "the CommonJS 'module' object in an ES module"); } @@ -309,6 +453,12 @@ export function lowerNodeModuleProperty( if (!direct && !nodeModuleType(lowerer, expr.expression)) return null; if (expr.questionDotToken) return null; const member = expr.name.text; + if (member === "exports" && direct && moduleExportsAlwaysTruthy(lowerer, expr)) + return boolLit(true, locOf(expr)); + if (member === "exports" && direct) { + const value = lowerCjsExportsObjectValue(lowerer, expr); + if (value) return value; + } if (member === "exports" || member === "require") return null; let moduleRef = direct ? cjsModuleRef(lowerer, expr.expression) diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 93f00596c..dbfa30f63 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -10,10 +10,24 @@ 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 { requireInitFlagStmt } from "./require-initialization.js"; import { isJsonRequireDecl } from "./json-require.js"; import { bindingSource } from "../binding-source.js"; import type { Lowerer } from "./lowerer.js"; +import { + cjsExportValueByName, + cjsNamespaceDestructureOf, + cjsTableMethodEntry, + inlineProgramRequireOf, + requireNestedPatternsOf, +} from "./cjs-inline-require.js"; +import { + cjsExportMemberWriteGlobalOf, + isCjsExportsIdentifier, + isFreshExportsReset, +} from "./cjs-exports-object.js"; +import { isDeadTscHelperDecl, tscInteropBindingOfDecl } from "./cjs-tsc-interop.js"; import { arrayValueRead, arrayValueStore, @@ -59,6 +73,7 @@ import { import { enforceLibBoundary, lowerSurplusCalls } from "./lib-boundary.js"; import { cjsExportAssignmentOf, + cjsExportChainOf, cjsExportDiscardReason, cjsExportTargetLiteral, isCjsJsFile, @@ -96,6 +111,7 @@ import { lowerForOfSearchParams, lowerForOfSet, } from "./containers/for-of.js"; +import { lowerForOfHelperChain } from "./containers/iterator-helpers.js"; import { objectIterOverIndexShape } from "./containers/indexed-objects.js"; import { strCharsCall } from "./containers/array-construction.js"; import { @@ -106,6 +122,7 @@ import { implicitLocalFnInfoOf, implicitLocalFnNodeOf, implicitMethodCallInfersReturn, + genericLocalClassBinding, } from "./generic-functions.js"; import { bindingNeverReassigned, @@ -147,7 +164,7 @@ import { } from "./builtins/module-bindings.js"; import { lowerNodeModuleCall } from "./builtins/module-resolution.js"; import { registerBuiltinCallableAlias } from "./builtins/callable-aliases.js"; -import { registerZlibClassAlias } from "./builtins/compression.js"; +import { builtinClassAliasOwnsNoSlot, registerBuiltinClassAlias } from "./builtins/class-values.js"; import { lowerEnumDeclaration } from "./lower-enums.js"; import { abstractPropertyDeclOf, @@ -662,15 +679,20 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir out, }; lowerer.activeStmtLists.push(entry); + const inFunction = stmts.length > 0 && enclosedByFunction(stmts[0]!); + let unreachable = false; try { for (let i = 0; i < stmts.length; i++) { const stmt = stmts[i]!; 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"); } const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { let lowered: IrStmt | IrStmt[] | null; let appended = false; @@ -755,6 +777,11 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir }, }); if (fence) out.push(fence); + } else if (deferFences) { + // Inside a capture buffer the owner keeps the diagnostics, but + // the statement still must not vanish from a result it keeps. + const fence = lowerer.capturedRuntimeFence(sinkMark, stmt); + if (fence) out.push(fence); } } } @@ -781,6 +808,54 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir return out; } +/** Whether a statement list belongs to a function body (at any block + * depth) rather than to a module's top level. */ +function enclosedByFunction(stmt: ts.Statement): boolean { + let owner: ts.Node | undefined = stmt.parent; + while (owner !== undefined && !ts.isFunctionLike(owner) && !ts.isSourceFile(owner)) + owner = owner.parent; + return owner !== undefined && ts.isFunctionLike(owner); +} + +/** `return`, `throw`, `break` and `continue`: the rest of the enclosing + * statement list never runs. */ +function isAbruptStatement(stmt: ts.Statement): boolean { + return ( + ts.isReturnStatement(stmt) || + ts.isThrowStatement(stmt) || + ts.isBreakStatement(stmt) || + ts.isContinueStatement(stmt) + ); +} + +/** Statements after an abrupt statement in a function body that lowering + * skips: they never execute, so neither their unsupported constructs nor the + * functions they reference become part of the program (production builds of + * libraries keep development-only code behind `throw` stubs this way). + * Hoisting keeps its meaning: a reference above the abrupt statement to a + * later function declaration lowers that declaration eagerly + * (predeclareForwardFnDecl), and references to a later `var`, `let` or + * `const` binding resolve through the forward-hoisting machinery to its + * undefined or TDZ state, which skipping its initializer preserves. Class, + * enum, module and import/export declarations and generic function + * declarations still lower. */ +function skipsWhenUnreachable(stmt: ts.Statement): boolean { + if (ts.isFunctionDeclaration(stmt)) return stmt.typeParameters === undefined; + if (ts.isVariableStatement(stmt)) + return !(stmt.modifiers ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword); + return !( + ts.isClassDeclaration(stmt) || + ts.isEnumDeclaration(stmt) || + ts.isModuleDeclaration(stmt) || + ts.isImportDeclaration(stmt) || + ts.isImportEqualsDeclaration(stmt) || + ts.isExportDeclaration(stmt) || + ts.isExportAssignment(stmt) || + ts.isInterfaceDeclaration(stmt) || + ts.isTypeAliasDeclaration(stmt) + ); +} + const TDZ_KINDS = new Set([ "func", "string", @@ -846,6 +921,9 @@ export function predeclareForwardCapture(lowerer: Lowerer, symbol: ts.Symbol): b if (!decl.initializer) return false; const flags = ts.getCombinedNodeFlags(decl); if ((flags & ts.NodeFlags.BlockScoped) === 0 || (flags & ts.NodeFlags.Using) !== 0) return false; + // `const E = EventEmitter` has no storage to box: uses resolve at + // compile time (builtinClassAliasOf). + if (builtinClassAliasOwnsNoSlot(lowerer, symbol)) return false; // MODULE-scope bindings are pre-registered globals (collectGlobals): // references resolve through globalOf after the local search fails, so // a TDZ box here would SHADOW the global and never fill (the top-level @@ -1546,6 +1624,16 @@ function lowerVarDeclarator( const init = lowerer.requireInitStmt(target.spec, decl); return init ? [init] : []; } + // tsc's interop bindings (`const x_1 = __importDefault(require("./m"))`) + // are require bindings (cjs-tsc-interop.ts): the module's %init here. + if (ts.isSourceFile(stmt.parent)) { + const interop = tscInteropBindingOfDecl(lowerer, decl); + if (interop !== null) { + const init = lowerer.requireInitStmt(interop.spec, decl); + return init ? [init] : []; + } + if (isDeadTscHelperDecl(lowerer, stmt)) return []; + } // CommonJS require declarations (JS files): at the module's top // level the BINDINGS are alias plumbing (no storage; // resolveValueSymbol routes the reads), but the require itself is @@ -1566,7 +1654,21 @@ function lowerVarDeclarator( if (lowerer.externalTypes.has(spec)) lowerer.externalHostFence(spec, decl, false); if (ts.isSourceFile(stmt.parent)) { const init = lowerer.requireInitStmt(spec, decl); - return init ? [init] : []; + const out: IrStmt[] = init ? [init] : []; + // Nested patterns destructure the named export's value right after + // the module evaluated (`const { codes: { ERR_X } } = require(...)`). + for (const element of requireNestedPatternsOf(decl)) { + const key = element.propertyName as ts.Identifier; + const value = lowerer.lowerExpr(key); + lowerer.lowerBindingPattern( + element.name as ts.ObjectBindingPattern, + () => lowerer.lowerExpr(key), + value.type, + isLet, + out, + ); + } + return out; } // A nested BUILTIN require (`get inFreeBSDJail() { const { execSync } // = require('child_process'); ... }` — test/common's lazy-getter @@ -1576,7 +1678,9 @@ function lowerVarDeclarator( // Relative requires keep the fence: their module-init-at-position // semantics needs storage this model doesn't represent. if (canonicalBuiltinModule(spec) !== null) { - return []; + return canonicalBuiltinModule(spec) === "punycode" + ? [lowerer.punycodeRequireWarning(decl)] + : []; } lowerer.unsupported( "SC1090", @@ -1593,6 +1697,35 @@ function lowerVarDeclarator( // `const { createSign } = crypto` over a builtin NAMESPACE binding: // alias plumbing (builtinImportOf routes the reads) — no statement. if (builtinNamespaceDestructureModuleOf(lowerer, decl) !== null) return []; + // `const { A, B } = ns` over a CommonJS module binding: each name + // snapshots the named export's value here (cjs-inline-require.ts). + { + const nsDestructure = cjsNamespaceDestructureOf(lowerer, decl); + if (nsDestructure !== null) { + const out: IrStmt[] = []; + for (const element of nsDestructure.elements) { + const target = lowerer.globalOf(element.name); + const value = cjsExportValueByName(lowerer, nsDestructure.dep, element.key, element.name); + if (target === null || value === null) { + lowerer.unsupported( + "SC1090", + element.name, + `destructuring the export '${element.key}' of a CommonJS module binding (it has no static value)`, + ); + } + out.push({ + kind: "assign", + localId: target.id, + value: typeEquals(value.type, target.type) + ? value + : lowerer.coerceInto(element.name, value, target.type), + loc: locOf(element.name), + ...("tdz" in target && target.tdz ? { initializes: true as const } : {}), + }); + } + return out; + } + } return lowerer.lowerDestructuringDecl(decl, isLet); } // STORED protocol values with statically closed representations: @@ -3277,6 +3410,9 @@ function stdlibGlobalTokenDestructure( token: string; alias: string | null; g: IrGlobal | undefined; + /** A data member's value (`const { env } = process`), bound instead + * of a token. */ + value?: IrExpr; }[] = []; for (const el of decl.name.elements) { if (el.name === undefined) continue; @@ -3312,8 +3448,9 @@ function stdlibGlobalTokenDestructure( "destructuring with computed keys that do not fold to one property name", ); } - let token: string; + let token = ""; let alias: string | null = null; + let value: IrExpr | null = null; if (globalName === "globalThis") { // The member must BE a stdlib global (user script vars are // properties of `typeof globalThis` too — their values are real @@ -3333,6 +3470,13 @@ function stdlibGlobalTokenDestructure( token = `[builtin console.${propName}]`; } else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) { token = `[builtin ${globalName}.${propName}]`; + } else if ( + globalName === "process" && + ts.isIdentifier(el.name) && + (value = processDataMemberValue(lowerer, propName, el.name, locOf(el))) !== null + ) { + // A data member reads its value once, exactly like the spelled + // `process.` read the destructuring performs. } else { lowerer.unsupported( "SC1031", @@ -3349,6 +3493,10 @@ function stdlibGlobalTokenDestructure( ); } const g = lowerer.globalsBySymbol.get(symbol); + if (value !== null) { + binds.push({ name: el.name, token, alias, g, value }); + continue; + } if (g !== undefined && g.type.kind !== "string" && g.type.kind !== "dyn") { lowerer.unsupported( "SC1031", @@ -3361,6 +3509,21 @@ function stdlibGlobalTokenDestructure( const out: IrStmt[] = []; for (const b of binds) { const loc = locOf(b.name); + if (b.value !== undefined) { + if (b.g !== undefined) { + out.push({ + kind: "assign", + localId: b.g.id, + value: lowerer.coerceInto(b.name, b.value, b.g.type), + ...(b.g.tdz ? { initializes: true as const } : {}), + loc, + }); + } else { + const local = lowerer.declareLocal(b.name, b.name.text, b.value.type, isLet); + out.push({ kind: "varDecl", localId: local.id, init: b.value, loc }); + } + continue; + } const token: IrExpr = { kind: "strLit", value: b.token, type: STRING, loc }; if (b.alias !== null) { const symbol = lowerer.checker.getSymbolAtLocation(b.name); @@ -4392,7 +4555,7 @@ export function lowerVarDecl( // reads reuse its interned closure identity. if (!isLet && registerOverloadedCallableAlias(lowerer, decl)) return null; if (!isLet && registerBuiltinCallableAlias(lowerer, decl.name, decl.initializer)) return null; - if (registerZlibClassAlias(lowerer, decl.name, decl.initializer)) return null; + if (registerBuiltinClassAlias(lowerer, decl.name, decl.initializer)) return null; // `const knownBy = (cmd) => ...` — an IMPLICIT-ANY function-value // binding in npm-static JS (function-scope bindings register here, in @@ -4481,16 +4644,21 @@ export function lowerVarDecl( if (wrapped) return { kind: "assign", localId: g.id, value: wrapped, loc: locOf(decl) }; return null; } - const raw = lowerVariableInitializer( - lowerer, - decl.initializer, - g.type.kind === "dyn" && - (isNativeBuiltinValueInitializer(lowerer, decl.initializer) || - ts.isArrayLiteralExpression(decl.initializer) || - ts.isObjectLiteralExpression(decl.initializer)) - ? DYN - : undefined, - ); + const guardedRead = isGuardedRecordRead(lowerer, decl) + ? lowerAbsenceProbe(lowerer, decl.initializer) + : null; + const raw = + guardedRead ?? + lowerVariableInitializer( + lowerer, + decl.initializer, + g.type.kind === "dyn" && + (isNativeBuiltinValueInitializer(lowerer, decl.initializer) || + ts.isArrayLiteralExpression(decl.initializer) || + ts.isObjectLiteralExpression(decl.initializer)) + ? DYN + : undefined, + ); if ( isJsSourceFile(decl.getSourceFile()) && !decl.type && @@ -4866,6 +5034,9 @@ export function lowerVarDecl( ) { type = init.type; } + // `const B = factory(1)` inside a generic instance: the factory's local + // class for the instance the call names. + if (!type && !isLet) type = genericLocalClassBinding(lowerer, decl.name); if (!type) { // The JS declaration fallback (irTypeOf's story): the binding holds // the checked-dynamic kind when even the initializer's type has no @@ -5286,29 +5457,111 @@ export function jsBindingHasOpenWrites(lowerer: Lowerer, decl: ts.VariableDeclar } function immediatelyGuardedAbsenceProbe(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { - if (decl.type || !ts.isIdentifier(decl.name) || !decl.initializer) return false; + const guard = immediateAbsenceGuard(lowerer, decl); + // `if (!x)`: the falsy test sees a missing value. + if (guard === "falsy") return true; + // `if (x === undefined)` and its spellings compare the binding with the + // missing value itself, so the read must be able to produce it. Only + // index-signature record reads take this form: array reads already + // carry their undefined arm, and their ordinary lowering keeps its + // bounds proofs. + return guard === "compare" && isIndexSignatureRecordRead(lowerer, decl); +} + +/** A file-scope or block-scope `let e = map[k]` over an index-signature + * record whose next statement guards `e` for absence (`if (e === + * undefined)`, `if (!e)`, ...): the get-or-create idiom. Its read keeps the + * missing key as undefined even without noUncheckedIndexedAccess. */ +export function isGuardedRecordRead(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { + return immediateAbsenceGuard(lowerer, decl) !== null && isIndexSignatureRecordRead(lowerer, decl); +} + +function isIndexSignatureRecordRead(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { + if (!decl.initializer) return false; + let init = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (!ts.isElementAccessExpression(init)) return false; + // Under noUncheckedIndexedAccess the checker already spells the missing + // arm, and the ordinary read carries it. + const read = lowerer.typeOf(init); + if ( + (read.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 || + (read.isUnionType() && + ts.constituentTypes(read).some((t) => (t.flags & ts.TypeFlags.Undefined) !== 0)) + ) + return false; + const receiver = lowerer.mapTypeOf(lowerer.typeOf(init.expression)); + const shape = receiver?.kind === "record" ? lowerer.shapes.get(receiver.shapeId) : undefined; + return !!shape && !shape.tuple && !!shape.indexValue && shape.indexValue.kind !== "dyn"; +} + +/** How the statement right after an unannotated declaration tests the + * declared binding for absence: `if (!x)` ("falsy"), or a comparison with + * the missing value — `x === undefined`, `x == null`, `x !== void 0`, + * `typeof x === "undefined"` and their mirrored and negated spellings + * ("compare"). Null for anything else. */ +function immediateAbsenceGuard( + lowerer: Lowerer, + decl: ts.VariableDeclaration, +): "falsy" | "compare" | null { + if (decl.type || !ts.isIdentifier(decl.name) || !decl.initializer) return null; const statement = decl.parent?.parent; - if (!statement || !ts.isVariableStatement(statement)) return false; + if (!statement || !ts.isVariableStatement(statement)) return null; const container = statement.parent; const statements = ts.isBlock(container) || ts.isSourceFile(container) ? container.statements : null; - if (!statements) return false; + if (!statements) return null; const index = statements.indexOf(statement); const next = index >= 0 ? statements[index + 1] : undefined; - if (!next || !ts.isIfStatement(next)) return false; - let condition = next.expression; - while (ts.isParenthesizedExpression(condition)) condition = condition.expression; - if ( - !ts.isPrefixUnaryExpression(condition) || - condition.operator !== ts.SyntaxKind.ExclamationToken - ) - return false; - let operand = condition.operand; - while (ts.isParenthesizedExpression(operand)) operand = operand.expression; - return ( - ts.isIdentifier(operand) && - lowerer.resolveValueSymbol(operand) === lowerer.resolveValueSymbol(decl.name) - ); + if (!next || !ts.isIfStatement(next)) return null; + const binding = lowerer.resolveValueSymbol(decl.name); + const peel = (node: ts.Expression): ts.Expression => { + while (ts.isParenthesizedExpression(node)) node = node.expression; + return node; + }; + const isBinding = (node: ts.Expression): boolean => { + const operand = peel(node); + return ts.isIdentifier(operand) && lowerer.resolveValueSymbol(operand) === binding; + }; + const condition = peel(next.expression); + if (ts.isPrefixUnaryExpression(condition)) + return condition.operator === ts.SyntaxKind.ExclamationToken && isBinding(condition.operand) + ? "falsy" + : null; + if (!ts.isBinaryExpression(condition)) return null; + const op = condition.operatorToken.kind; + const strict = + op === ts.SyntaxKind.EqualsEqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsEqualsToken; + const loose = + op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken; + if (!strict && !loose) return null; + const isUndefined = (node: ts.Expression): boolean => { + const operand = peel(node); + if (ts.isVoidExpression(operand)) return true; + return ( + ts.isIdentifier(operand) && + operand.text === "undefined" && + (lowerer.typeOf(operand).flags & ts.TypeFlags.Undefined) !== 0 + ); + }; + const isNull = (node: ts.Expression): boolean => peel(node).kind === ts.SyntaxKind.NullKeyword; + const missing = (node: ts.Expression): boolean => isUndefined(node) || (loose && isNull(node)); + const isTypeofBinding = (node: ts.Expression): boolean => { + const operand = peel(node); + return ts.isTypeOfExpression(operand) && isBinding(operand.expression); + }; + const isUndefinedTag = (node: ts.Expression): boolean => { + const operand = peel(node); + return ts.isStringLiteralLike(operand) && operand.text === "undefined"; + }; + const { left, right } = condition; + return (isBinding(left) && missing(right)) || + (missing(left) && isBinding(right)) || + (isTypeofBinding(left) && isUndefinedTag(right)) || + (isUndefinedTag(left) && isTypeofBinding(right)) + ? "compare" + : null; } function falsyExitRuntimeOptionalLocals(lowerer: Lowerer, stmt: ts.IfStatement): IrLocal[] { @@ -5925,6 +6178,49 @@ function lowerCjsExportStatement( if (whole?.kind !== "table" || cjsExportDiscardReason(stmt) !== null) continue; root = lowerer.globalsByDeclNode.get(whole.expr); } + // A CHAIN `exports.a = exports.b = value`: Node evaluates the value + // once, assigns the innermost member first, and every member holds + // the same value. Each member is its own export global + // (collectGlobals registers the inner ones too). + { + const chain = cjsExportChainOf(cjs.expr); + if (chain !== null && root?.type.kind !== "dyn") { + const globalOfName = (nameNode: ts.Identifier): IrGlobal => { + const symbol = + lowerer.checker.getSymbolAtLocation(nameNode) ?? + lowerer.cjsModuleExportSymbol(sf, nameNode.text); + const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; + if (!g) { + lowerer.lowerExpr(chain.value); + lowerer.badType(chain.value, lowerer.typeOf(chain.value)); + } + return g; + }; + const inner = chain.names[chain.names.length - 1]!; + const innerG = globalOfName(inner); + const body: IrStmt[] = [ + { + kind: "assign", + localId: innerG.id, + value: lowerer.lowerExprExpecting(chain.value, innerG.type), + loc: locOf(chain.value), + }, + ]; + for (let i = chain.names.length - 2; i >= 0; i--) { + const g = globalOfName(chain.names[i]!); + const read: IrExpr = { kind: "varRef", localId: innerG.id, type: innerG.type, loc }; + body.push({ + kind: "assign", + localId: g.id, + value: typeEquals(g.type, innerG.type) + ? read + : lowerer.coerceInto(chain.value, read, g.type), + loc, + }); + } + return { kind: "block", body, loc }; + } + } return assignExport(cjs.name, cjs.value, root); } let tableObj = cjs.obj; @@ -5990,6 +6286,30 @@ function lowerCjsExportStatement( body.push(assignExport(prop.name, prop.initializer)); continue; } + // METHOD entries: the export global holds the method's function, + // created at this statement like a function-expression entry. Node + // calls it with `this` bound to the table, which the static export + // table cannot represent, so a body that uses `this` keeps a fence. + { + const method = cjsTableMethodEntry(prop); + if (method !== null && !resolvedTarget) { + lowerer.rejectThisInObjectMethod(method.body); + const symbol = + lowerer.checker.getSymbolAtLocation(method.name) ?? + lowerer.cjsModuleExportSymbol(cjs.expr.getSourceFile(), method.name.text); + const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; + if (g) { + const fn = lowerer.lowerLambda(method); + body.push({ + kind: "assign", + localId: g.id, + value: typeEquals(fn.type, g.type) ? fn : lowerer.coerceInto(method, fn, g.type), + loc: locOf(method), + }); + continue; + } + } + } // ACCESSOR entries: no storage and no code here — reads lift the // getter as a module-level function and call it per read // (cjsExportAccessorRead); writes through the setter keep their @@ -6005,7 +6325,15 @@ function lowerCjsExportStatement( if (ts.isSpreadAssignment(prop)) { let inner: ts.Expression = prop.expression; while (ts.isParenthesizedExpression(inner)) inner = inner.expression; - if (requireSpecOf(inner) !== null) continue; + if (requireSpecOf(inner) !== null) { + // The spread still EVALUATES the module, at its place among the + // table's entries (a program module joins the graph through this + // inline require; cjs-inline-require.ts). + const req = inlineProgramRequireOf(lowerer, inner); + const init = req !== null ? lowerer.requireInitStmt(req.spec, req.call) : null; + if (init !== null && !resolvedTarget) body.push(init); + continue; + } } lowerer.unsupported( "SC1090", @@ -6038,6 +6366,16 @@ function lowerCjsSingleValueExport( ): IrStmt | null { let rhs: ts.Expression = expr.right; while (ts.isParenthesizedExpression(rhs)) rhs = rhs.expression; + // `module.exports = require("./x")`: the module re-exports x's exports. + // Requirers resolve through the checker's alias to x's own export + // machinery; this statement is x's evaluation at this position. + { + const req = inlineProgramRequireOf(lowerer, rhs); + // With later member exports (`module.exports.extra = …`) the module + // extends that object, so it keeps the factory root registration. + if (req !== null && !lowerer.globalsByDeclNode.has(expr)) + return lowerer.requireInitStmt(req.spec, req.call) ?? { kind: "block", body: [], loc }; + } if (ts.isIdentifier(rhs)) { const sym = lowerer.resolveValueSymbol(rhs); if (!sym) return null; @@ -6048,6 +6386,17 @@ function lowerCjsSingleValueExport( ) { return { kind: "block", body: [], loc }; } + // A require binding (`const x = require("./x"); module.exports = x`) + // re-exports that module exactly like the inline form (the binding's + // own statement already evaluated it), and a builtin global + // (`module.exports = TypeError`) is the global itself: requirer + // bindings alias straight through to it, so nothing is stored. + if ( + lowerer.checker.declarationsOf(sym).some((d) => ts.isSourceFile(d)) || + lowerer.isStdlibSymbol(sym) + ) { + return { kind: "block", body: [], loc }; + } const g = lowerer.globalsBySymbol.get(sym); if (g) { if (g.mutable) { @@ -6269,6 +6618,21 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm ); } if (topLevelJs && ts.isExpressionStatement(stmtNode)) { + if (isFreshExportsReset(lowerer, stmtNode)) + return { kind: "block", body: [], loc: locOf(expr) }; + // Any other `exports = …` rebinds the wrapper's exports binding: the + // members later attached through it belong to the new value. + if ( + ts.isBinaryExpression(expr) && + expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && + isCjsExportsIdentifier(lowerer, expr.left) + ) { + lowerer.unsupported( + "SC1090", + expr.left, + "rebinding 'exports' (exports = module.exports = value): members attached through 'exports' afterwards are properties of that value, which the static export table does not model — write module.exports. = … and module.exports = value", + ); + } const cjs = cjsExportAssignmentOf(stmtNode); if (cjs) return lowerCjsExportStatement(lowerer, cjs); // The tsc CJS interop stamp — `Object.defineProperty(exports, @@ -6810,6 +7174,19 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm const parts = lowerDestructuringAssignParts(lowerer, expr.left, expr.right, locOf(expr)); return { kind: "block", body: parts.stmts, loc: locOf(expr) }; } + // `exports.x = v` inside a function updates the member's export + // global (cjs-exports-object.ts). + if (ts.isPropertyAccessExpression(expr.left)) { + const g = cjsExportMemberWriteGlobalOf(lowerer, expr.left); + if (g) { + return { + kind: "assign", + localId: g.id, + value: lowerer.lowerExprExpecting(expr.right, g.type), + loc: locOf(expr), + }; + } + } if (!ts.isIdentifier(expr.left)) { lowerer.unsupported("SC1090", expr.left, "assignment to non-variables"); } @@ -8170,6 +8547,12 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { } } } + // A head that is a chain of iterator-helper stages runs as one loop + // over the chain's source. + { + const fused = lowerForOfHelperChain(lowerer, stmt, labels); + if (fused) return fused; + } // `for (const k of m.keys())` / `.values()` / `.entries()`: the // iterator methods consumed DIRECTLY by a for-of head ride the // container's live walk — exactly `for..of m` with the projection diff --git a/packages/compiler/src/frontend/lowering/lower-test.ts b/packages/compiler/src/frontend/lowering/lower-test.ts index b9d4e8df1..e36518559 100644 --- a/packages/compiler/src/frontend/lowering/lower-test.ts +++ b/packages/compiler/src/frontend/lowering/lower-test.ts @@ -473,7 +473,7 @@ function lowerHook( return { kind: "libCall", fn: "test.hook", - args: [numLit(which, loc), cb, numLit(flags, loc)], + args: [numLit(which, loc), cb, numLit(flags, loc), strLit(atStringOf(expr), loc)], type: VOID, loc, }; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 40cf40de9..9c6a90ebc 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,3 +1,4 @@ +import { jsDynamicDeclarations } from "./js-dynamic-objects.js"; import { lifetimeRegionType, lowerLifetimeRegionCall, @@ -25,6 +26,9 @@ 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 { discoverWasmModules, type WasmModuleReport } from "../../wasm/discover.js"; +import { resolveRequireRuntime } from "../runtime-resolve.js"; import { lowerKeyObjectCall } from "./builtins/key-object.js"; import { hashStreamOf } from "./builtins/hash-stream.js"; import { lowerNetAddressCall, lowerNetAddressNew } from "./builtins/net-address.js"; @@ -110,6 +114,13 @@ import { ClassDynamicDispatch, trackClassFieldCreation } from "./class-dynamic-d import { ClassProtocols } from "./class-protocols.js"; import { earlyReadVarSymbols } from "./var-hoisting.js"; import { recordTextCodecClass } from "../../ir/ir.js"; +import { assignLegacyTiers, type DynamicFunction } from "../../ir/tiers.js"; +import { + analyzeTiers, + dynamicStatementCount, + type TierMode, + type TierReport, +} from "../tier-analysis.js"; import { DeferredModuleInitializers } from "./deferred-module-initializers.js"; import { finalizeClassMethodValues } from "./class-method-values.js"; import type { ClassSymbolKey } from "./symbol-fields.js"; @@ -134,6 +145,7 @@ import { resolve } from "node:path"; import { bindingSource } from "../binding-source.js"; import { tsgoPath } from "../dts-paths.js"; import * as ts from "../ts7/adapter.js"; +import { erasedTypeOnlyImport } from "../type-only-imports.js"; import { literalValues } from "../literal-values.js"; import { discriminantOwners, literalUnionArm, type UnionLiteral } from "../union-discriminants.js"; import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js"; @@ -222,9 +234,11 @@ import { containsUnion, describeComponentBlocker, describeRecordMemberBlocker, + erasedOverloadMemberType, formatIrType, FETCH_AMBIENT_TYPES, isOsConstantsSymbol, + isOverloadedCallableMemberType, isUnitOnlyTsType, isWorkerHandleType, jsOpenObjectType, @@ -253,6 +267,7 @@ import { stdlibGlobalMember, nodeTypesOnlySymbol, } from "./surfaces.js"; +import { type BuiltinClassRef, builtinClassAliasOf } from "./builtins/class-values.js"; import { type FileParts, splitFiles, @@ -270,7 +285,14 @@ import { appendForkModules, appendWorkerModules, } from "./lower-modules.js"; -import { prepareCjsModuleGraph } from "./lower-node-module.js"; +import { prepareCjsModuleGraph, usesCjsTopLevelThis } from "./lower-node-module.js"; +import { + cjsNamespaceModule, + inlineNamespaceRequireOf, + inlineProgramRequireOf, +} from "./cjs-inline-require.js"; +import { tscInteropModuleOf } from "./cjs-tsc-interop.js"; +import { usesCjsExportsIdentity } from "./cjs-exports-object.js"; import { registerBuiltinEventTargetClasses } from "./lower-event-target.js"; import { type ClassInfo, @@ -357,6 +379,7 @@ import { lowerGenericFnValue, inferTypeParamBindings, lowerGenericInstance, + genericLocalClassBinding, } from "./generic-functions.js"; import { bindingNeverReassigned } from "./binding-analysis.js"; import type { InstantiationPath } from "./instantiation-path.js"; @@ -532,6 +555,7 @@ import { lowerForOf, lowerForStatement, matchGroupsPatternSource, + isGuardedRecordRead, } from "./lower-stmts.js"; import { type FieldTarget, @@ -629,6 +653,9 @@ export interface LowerStats { /** Functions whose signature couldn't be analyzed (bodies not counted). */ functionsSkipped: number; + /** Coverage only: reached statements that compile into dynamic-tier + * functions (tier-analysis.ts), a subset of the compiled statements. */ + statementsDynamic?: number; } /** Every identifier a binding name binds: the identifier itself, or all @@ -700,9 +727,16 @@ export interface LowerResult { /** Coverage only (LowerOptions.coverage): the unreached remainder, * lowered in a throwaway pass — blockers in it can never fail a build. */ unreached?: { diagnostics: ScrDiagnostic[]; stats: LowerStats; runtimeFences?: ScrDiagnostic[] }; + /** Coverage only: the WebAssembly modules the build can see (wasm/discover.ts). */ + wasmModules?: WasmModuleReport[]; /** Compile-time-detectable behavior differences from Node (SC6xxx * divergence warnings) in the program's own modules. Never failures. */ divergences?: ScrDiagnostic[]; + /** Functions lowering placed in the dynamic tier (ir/tiers.ts), present + * when there are any. */ + dynamicFunctions?: DynamicFunction[]; + /** LowerOptions.tierReport: the report-only tier assignment. */ + tiers?: TierReport; } export interface LowerOptions { @@ -715,6 +749,8 @@ export interface LowerOptions { * diagnostics and stats under `unreached` — the whole-program analysis * builds deliberately gave up. */ coverage?: boolean; + /** Coverage only: module files named by --wasm (absolute). */ + wasmFiles?: readonly string[]; /** The platform the build TARGETS ("win32" under a windows cross triple, * the host platform otherwise — see buildTargetPlatform in index.ts). * The whole program compiles for one platform, so the platform-keyed @@ -727,6 +763,8 @@ export interface LowerOptions { * resolution walk and named-import link checks): the program * compiles to that startup crash. */ startupCrash?: StartupCrash | null; + /** LoadResult.inlineRequireCycleCalls. */ + inlineRequireCycleCalls?: ReadonlySet | undefined; /** LIBRARY mode's reachability roots: the profile-mapped exports of the * entry module. Executable builds root at the entry's top level alone * (an unreferenced export dead-strips); a library's exports are called from @@ -752,6 +790,12 @@ export interface LowerOptions { /** The mapped entries plus relative declaration dependencies, attributed * to their owning external specifier. */ externalTypeSpecifiersByFile?: ReadonlyMap; + /** `--tier` (tier-analysis.ts); "auto" when absent. */ + tierMode?: TierMode; + /** Coverage only: compute the tier-assignment report (`--explain-dynamic`). + * `preflight` holds the import and package refusals coverage reports + * beside the lowering's own blockers; they request module demotions. */ + tierReport?: { preflight: readonly ScrDiagnostic[] }; } interface RuntimeFenceFallback { @@ -813,6 +857,8 @@ export interface LowererMode { * 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). */ @@ -827,6 +873,8 @@ export interface LowererMode { externalTypes?: ReadonlyMap; /** LowerOptions.externalTypeSpecifiersByFile, shared by every pass. */ externalTypeSpecifiersByFile?: ReadonlyMap; + /** LowerOptions.tierMode, shared by every pass. */ + tierMode?: TierMode; } function directExternalTypeSpecifiersByFile( @@ -918,17 +966,20 @@ export function lowerToIr( const externalTypes = options.externalTypes ?? new Map(); const externalTypeSpecifiersByFile = options.externalTypeSpecifiersByFile ?? directExternalTypeSpecifiersByFile(externalTypes); + const tierMode = options.tierMode ?? "auto"; const validation = new Lowerer(program, entry, moduleOrder, { frontendServices: options.frontendServices, projectCheckJs: options.projectCheckJs ?? false, targetPlatform, startupCrash, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, ffiImports, libraryCallbacks, forkTargets, workerTargets, externalTypes, externalTypeSpecifiersByFile, + tierMode, }); const ffiValidation = validateFfiImports(validation); timing("ffi-validate"); @@ -945,12 +996,14 @@ export function lowerToIr( projectCheckJs: options.projectCheckJs ?? false, targetPlatform, startupCrash, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, ffiImports, libraryCallbacks, forkTargets, workerTargets, externalTypes, externalTypeSpecifiersByFile, + tierMode, ffiBindingSymbols: ffiValidation.symbolsByName, }); for (const d of extraModuleCycleDiags) reachableEmit.pushDiag(d); @@ -974,6 +1027,7 @@ export function lowerToIr( reachable, targetPlatform, startupCrash, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, ffiImports, libraryCallbacks, forkTargets, @@ -981,6 +1035,7 @@ export function lowerToIr( ffiBindingSymbols: ffiValidation.symbolsByName, externalTypes, externalTypeSpecifiersByFile, + tierMode, }); for (const d of extraModuleCycleDiags) emit.pushDiag(d); for (const d of ffiValidation.diagnostics) emit.pushDiag(d); @@ -1031,6 +1086,7 @@ export function lowerToIr( reachable, remainder: true, alreadyFlushed: resultLowerer.flushedSymbols, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, targetPlatform, ffiImports, libraryCallbacks, @@ -1039,19 +1095,61 @@ export function lowerToIr( ffiBindingSymbols: ffiValidation.symbolsByName, externalTypes, externalTypeSpecifiersByFile, + tierMode, }); const rem = remainder.run(); + timing("coverage-remainder"); + // Tiers (tier-analysis.ts): reached statements in dynamic-tier functions, + // and with tierReport the report-only demotion analysis. + const dynamicFunctions = resultLowerer.dynamicBodies; + const tiers = + options.tierReport === undefined + ? undefined + : analyzeTiers(program, { + mode: tierMode, + dynamicFunctions, + reached: [...options.tierReport.preflight, ...result.diagnostics], + deferred: result.runtimeFences, + unreached: [...rem.diagnostics, ...rem.runtimeFences], + }); + // A dynamic function's own statements can include ones that failed. + const statementsDynamic = Math.min( + tiers?.statementsDynamic ?? dynamicStatementCount(program, dynamicFunctions), + result.stats.statementsTotal - result.stats.statementsFailed, + ); + if (tiers !== undefined) tiers.statementsDynamic = statementsDynamic; + timing("tier-analysis", { dynamic: dynamicFunctions.length }); // Unreached JS statements whose fences deferred to runtime count as // failed statements in the remainder's stats, so their diagnostics must // travel with them: a failed statement is never left unexplained. - return { + const covered: LowerResult = { ...result, + stats: { ...result.stats, statementsDynamic }, unreached: { diagnostics: rem.diagnostics, stats: rem.stats, ...(rem.runtimeFences.length > 0 ? { runtimeFences: rem.runtimeFences } : {}), }, }; + 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; + }, + }); + timing("wasm-discovery", { modules: wasmModules.length }); + if (wasmModules.length > 0) covered.wasmModules = wasmModules; + return covered; } /** The native namespace type produced by a literal static import() of a @@ -1565,6 +1663,15 @@ export class Lowerer { readonly checker: ts.TypeChecker; readonly diags: ScrDiagnostic[] = []; readonly fnSigsBySymbol = new Map(); + /** Per file: whether it assigns `exports` and `module.exports` + * (cjs-exports-object.ts). */ + readonly cjsExportsAssignments = new Map< + ts.SourceFile, + { exports: boolean; moduleExports: boolean } + >(); + /** Inline require() calls whose module edge closes a require cycle + * (LoadResult.inlineRequireCycleCalls). */ + readonly inlineRequireCycleCalls: ReadonlySet; /** Immutable callable aliases and compiler-generated adapters. The map * is symbol-keyed, so shadowing and cross-module imports keep the same * exact binding discipline as globals/functions. */ @@ -1791,6 +1898,9 @@ export class Lowerer { * and erased instances). */ erasingDepth = 0; localClassInstantiations: { owner: ts.FunctionLikeDeclaration; name: string }[] = []; + /** Instance types (by checker type id) of the local classes generic + * factory calls return, and the per-instance class each names. */ + readonly localClassInstanceTypes = new Map(); /** The ts-level twin of typeParamBindings, non-null while a CALL-keyed * instance body lowers: type-parameter symbol → the bound CHECKER type, * consulted where the mapped IrType has already widened away information @@ -1804,11 +1914,11 @@ export class Lowerer { * typeParamBindings — the checker has no `T` to substitute, so the * binding rides the node-type accessor instead of mapType. */ implicitParamTypes: Map | null = null; - /** Immutable bindings that name a zlib stream class (`const C = - * zlib.Gzip`), keyed by the binding's symbol: the class's - * node_zlib_mode. No runtime slot exists: new, typeof, .name and === - * resolve at compile time, and instanceof checks the stream's mode. */ - readonly zlibClassAliases = new Map(); + /** The builtin class each binding names (`const E = EventEmitter`, + * `const C = zlib.Gzip`), keyed by the binding's symbol; null for + * bindings that name none (builtinClassAliasOf). new, instanceof, + * typeof, .name and === resolve at compile time. */ + readonly builtinClassAliases = new Map(); /** Whether some net.Socket in this program may get an encoding: a * setEncoding call whose receiver is a socket or an untyped value. * Computed once on first use (null until then). */ @@ -1858,6 +1968,12 @@ export class Lowerer { * carry undefined at runtime. Identifier reads keep the union tag so * typeof and later boundaries observe the real value. */ readonly runtimeOptionalGlobals = new Set(); + /** Per CommonJS file using top-level `this`: the global holding the + * object that stands for its original exports (lowerCjsTopLevelThis). */ + readonly cjsExportsThis = new Map(); + /** Inline program-module require calls whose %init the enclosing member + * read or member call is already sequencing (cjs-inline-require.ts). */ + readonly inlineRequireInitDone = new Set(); /** Bindings whose unchecked string arithmetic can produce either a number * (NaN for an absent read) or a string. Preserve that result union at bare * reads instead of applying the checker-bare string narrowing adapter. */ @@ -2018,6 +2134,42 @@ export class Lowerer { }; } + /** True when `node`'s lowered value can hold a hidden `undefined` the + * checker type does not admit: an unchecked element or member read, a call + * whose lowered return admits undefined, or a binding whose storage the + * optional-read analysis marked. Whether the value actually carries the + * undefined arm is only known once it is lowered + * (runtimeOptionalSourceValue); this is the syntactic precondition. */ + isRuntimeOptionalOrigin(node: ts.Expression): boolean { + let origin: ts.Expression = node; + while (ts.isParenthesizedExpression(origin)) origin = origin.expression; + if (ts.isElementAccessExpression(origin) || ts.isPropertyAccessExpression(origin)) return true; + if (ts.isCallExpression(origin)) { + const name = ts.isPropertyAccessExpression(origin.expression) + ? origin.expression.name + : origin.expression; + const symbol = ts.isIdentifier(name) ? this.resolveValueSymbol(name) : undefined; + const signature = symbol ? this.fnSigsBySymbol.get(symbol) : undefined; + return ( + this.runtimeOptionalFunctionReturns.has(origin) || + (signature?.returnType.kind === "union" && + this.armTag(signature.returnType.unionId, UNDEFINED_T) >= 0) + ); + } + if (ts.isIdentifier(origin)) { + const local = this.resolveLocal(origin); + const root = local ? this.runtimeOptionalRootOf(local) : null; + const global = this.globalOf(origin); + return ( + (!!root && this.runtimeOptionalStorageLocals.has(root)) || + (!!global && + (this.isRuntimeOptionalGlobal(global) || + (global.type.kind === "union" && this.armTag(global.type.unionId, UNDEFINED_T) >= 0))) + ); + } + return false; + } + /** Recover the original optional union when a checked single-arm helper * was installed before a strict property consumer could name its member. * Recovery covers member reads and storage roots marked by the @@ -2026,32 +2178,7 @@ export class Lowerer { runtimeOptionalSourceValue(node: ts.Expression, value: IrExpr): IrExpr | null { const slot = this.deferredFieldSlot(value); if (slot) return slot; - let origin: ts.Expression = node; - while (ts.isParenthesizedExpression(origin)) origin = origin.expression; - let optionalOrigin = - ts.isElementAccessExpression(origin) || ts.isPropertyAccessExpression(origin); - if (ts.isCallExpression(origin)) { - const name = ts.isPropertyAccessExpression(origin.expression) - ? origin.expression.name - : origin.expression; - const symbol = ts.isIdentifier(name) ? this.resolveValueSymbol(name) : undefined; - const signature = symbol ? this.fnSigsBySymbol.get(symbol) : undefined; - optionalOrigin = - this.runtimeOptionalFunctionReturns.has(origin) || - (signature?.returnType.kind === "union" && - this.armTag(signature.returnType.unionId, UNDEFINED_T) >= 0); - } - if (ts.isIdentifier(origin)) { - const local = this.resolveLocal(origin); - const root = local ? this.runtimeOptionalRootOf(local) : null; - const global = this.globalOf(origin); - optionalOrigin = - (!!root && this.runtimeOptionalStorageLocals.has(root)) || - (!!global && - (this.isRuntimeOptionalGlobal(global) || - (global.type.kind === "union" && this.armTag(global.type.unionId, UNDEFINED_T) >= 0))); - } - if (!optionalOrigin) return null; + if (!this.isRuntimeOptionalOrigin(node)) return null; // A checked JavaScript indexed read can already be boxed. The checker // calls the element present, but that inference is not a runtime guard. @@ -2696,6 +2823,14 @@ export class Lowerer { /** Coverage remainder mode: the reachability gate inverts (see wantBody) * and no module is built. */ readonly remainder: boolean; + /** `--tier` (tier-analysis.ts). Stage S2 is report-only: both modes + * 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; + /** Functions finishModule placed in the dynamic tier (ir/tiers.ts). */ + dynamicFunctions: DynamicFunction[] = []; + /** Their IR, for coverage's dynamic statement count. */ + dynamicBodies: IrFunction[] = []; /** Deferred collection diagnostics (failed signatures/class shapes) by * declaration symbol: an unreached declaration must not fail the build, * so its diagnostics wait until a reference makes them relevant. */ @@ -2782,9 +2917,11 @@ export class Lowerer { this.frontendServices = mode.frontendServices; 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; @@ -2816,7 +2953,11 @@ export class Lowerer { this.mixinTypeContext && this.mixinTypeContext.classNode === decl ? { kind: "object", className: this.mixinTypeContext.className } : null, - localClassInstance: (decl) => { + localClassInstance: (decl, instanceType) => { + // A generic factory's local class, by the instance type a call of + // the factory produced (localClassResultInstance). + const instanceClass = instanceType && this.localClassInstanceTypes.get(instanceType.id); + if (instanceClass) return { kind: "object", className: instanceClass }; // Computed heritage can close over parameters of its factory. // A speculative checker query outside that factory must not lower // the extends expression in the caller's lexical scope. @@ -2871,7 +3012,8 @@ export class Lowerer { return null; } const className = this.classNamer(decl); - if (!this.collectingExprClasses.has(decl)) this.lowerClassExpressionInfo(decl); + if (!this.collectingExprClasses.has(decl) && !this.collectLocalClassForType(decl)) + return null; return { kind: "object", className }; }, mixinIntersectionInstance: (widened) => mixinIntersectionInstanceType(this, widened), @@ -2906,6 +3048,8 @@ export class Lowerer { this.fileTag.set(sf, sf === entry ? "" : `%m${i}.`); }); if (this.moduleOrder.length === 0) this.fileTag.set(entry, ""); + const jsDynamic = jsDynamicDeclarations(this.moduleOrder, this.checker); + if (jsDynamic.size > 0) this.typeCtx.jsDynamicDeclaration = (decl) => jsDynamic.has(decl); this.registerBuiltinErrorClasses(); registerBuiltinEmitterClass(this); registerBuiltinStreamClasses(this); @@ -3008,6 +3152,10 @@ export class Lowerer { return builtinStreamInfoOf(this, symbol); } + builtinClassAliasOf(symbol: ts.Symbol): BuiltinClassRef | null { + return builtinClassAliasOf(this, symbol); + } + /** Program-wide qualified name for a top-level declaration. */ qualify(sf: ts.SourceFile, name: string): string { return `${this.fileTag.get(sf) ?? ""}${name}`; @@ -3089,8 +3237,10 @@ export class Lowerer { ident.parent.name === ident ) { const recv = ident.parent.expression; - if (ts.isIdentifier(recv) && this.cjsLocalModuleBindingOf(recv)) { - const dep = this.localModuleBindingDepOf(recv); + if (this.cjsLocalModuleBindingOf(recv)) { + const dep = ts.isIdentifier(recv) + ? this.localModuleBindingDepOf(recv) + : this.cjsModuleRefDepOf(recv); const exported = dep ? this.cjsModuleExportSymbol(dep, ident.text) : undefined; // A createRequire call is typed as `any`, so tsgo can manufacture a // transient property symbol with no relation to the target module. @@ -3482,6 +3632,21 @@ export class Lowerer { return dep; } + /** The table module a non-identifier module reference names: an inline + * `require("./x")` (cjs-inline-require.ts) or tsc's unstamped + * `x_1.default` interop read (cjs-tsc-interop.ts). */ + cjsModuleRefDepOf(expr: ts.Expression): ts.SourceFile | null { + const inline = inlineNamespaceRequireOf(this, expr); + if (inline !== null) return inline.dep; + const interop = tscInteropModuleOf(this, expr); + return interop !== null && cjsNamespaceModule(this, interop.dep) ? interop.dep : null; + } + + /** The table module a CommonJS module binding identifier names. */ + cjsModuleBindingDepOf(expr: ts.Identifier): ts.SourceFile | null { + return this.localModuleBindingDepOf(expr); + } + /** The compiled module behind a require-like namespace binding: an * ordinary CommonJS require declaration, a canonical createRequire * declaration, or an ESM default import of CommonJS. One resolver keeps @@ -3494,6 +3659,10 @@ export class Lowerer { if (ts.isImportClause(decl)) return this.cjsDefaultImportDepOf(decl); if (!ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) || !decl.initializer) return null; + { + const interop = tscInteropModuleOf(this, expr); + if (interop !== null) return cjsNamespaceModule(this, interop.dep) ? interop.dep : null; + } const directSpec = requireSpecOf(decl.initializer); if (directSpec !== null) { if (isJsonRequireDecl(this, decl)) return null; @@ -3540,6 +3709,10 @@ export class Lowerer { if (wholeAssignment === undefined) return null; let rhs: ts.Expression = wholeAssignment.right; while (ts.isParenthesizedExpression(rhs)) rhs = rhs.expression; + // A pure re-export (`module.exports = require("./core")`) is the inner + // module's export table, read like any namespace binding. + if (inlineProgramRequireOf(this, rhs) !== null && !this.globalsByDeclNode.has(wholeAssignment)) + return null; if (ts.isIdentifier(rhs)) { const value = this.checker.getSymbolAtLocation(rhs); if (value) { @@ -3592,7 +3765,9 @@ export class Lowerer { * symbols → resolveValueSymbol); the bare value keeps the * namespace-object fence, like ESM namespace imports of builtins. */ cjsLocalModuleBindingOf(expr: ts.Expression): boolean { - if (!ts.isIdentifier(expr)) return false; + // An inline `require("./x")` over a table module reads its members like + // a binding (cjs-inline-require.ts sequences the module's %init). + if (!ts.isIdentifier(expr)) return this.cjsModuleRefDepOf(expr) !== null; const sym = this.checker.getSymbolAtLocation(expr); if (this.localModuleBindingDepOf(expr) === null) return false; if (this.createRequireWholeExportSymbolOf(expr) !== null) return false; @@ -3605,6 +3780,8 @@ export class Lowerer { // the object literal) keep the namespace reading — member accesses // resolve through the table's property symbols. if (sym && sym.flags & ts.SymbolFlags.Alias) { + // `module.exports = TypeError`: the binding IS the builtin global. + if (this.isStdlibSymbol(this.checker.getAliasedSymbol(sym))) return false; const d = this.checker.declarationsOf(this.checker.getAliasedSymbol(sym))[0]; if (d && (ts.isClassDeclaration(d) || ts.isFunctionDeclaration(d))) return false; if (d && ts.isVariableDeclaration(d)) { @@ -3702,6 +3879,16 @@ export class Lowerer { const id = `%g.${tag}%loaded`; this.moduleGuardOf.set(fp.sf, id); this.globalsList.push({ id, name: "%loaded", type: BOOL, mutable: true }); + if (usesCjsTopLevelThis(this, fp.sf) || usesCjsExportsIdentity(this, fp.sf)) { + const exportsThis: IrGlobal = { + id: `%g.${tag}%exportsThis`, + name: "%exportsThis", + type: DYN, + mutable: true, + }; + this.globalsList.push(exportsThis); + this.cjsExportsThis.set(fp.sf, exportsThis); + } } // A module is intrinsically async when an await/for-await occurs @@ -3821,6 +4008,65 @@ export class Lowerer { for (const fp of parts) if (!indexOf.has(fp.sf)) visit(fp.sf); } + /** node:punycode's load at a require() call: the module body reports + * DEP0040 when it first runs, unless isInsideNodeModules() finds the + * requiring frame in a dependency, and process.emitWarning delivers the + * 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); + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "punycode.warn", + args: [ + { kind: "boolLit", value: false, type: BOOL, loc }, + { kind: "boolLit", value: !fromDependency, type: BOOL, loc }, + ], + type: VOID, + loc, + }, + loc, + }; + } + + /** node:punycode's DEP0040 for a static ES import anywhere in the module + * graph: Node links builtins before any module evaluates, so the report + * comes first, before the program can register a 'warning' listener — + * whichever file imports it (a dependency's ES import has no requiring + * frame either). Prepended to the entry's %init. */ + punycodeLinkWarning(): IrStmt[] { + const imports = this.moduleOrder.some((sf) => + sf.statements.some( + (stmt) => + ((ts.isImportDeclaration(stmt) && !erasedTypeOnlyImport(stmt)) || + (ts.isExportDeclaration(stmt) && !stmt.isTypeOnly)) && + stmt.moduleSpecifier !== undefined && + ts.isStringLiteral(stmt.moduleSpecifier) && + canonicalBuiltinModule(stmt.moduleSpecifier.text) === "punycode", + ), + ); + if (!imports) return []; + const loc: SrcLoc = { file: this.entry.fileName, start: 0, end: 0 }; + return [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "punycode.warn", + args: [ + { kind: "boolLit", value: true, type: BOOL, loc }, + { kind: "boolLit", value: true, type: BOOL, loc }, + ], + type: VOID, + loc, + }, + loc, + }, + ]; + } + /** The lowering of a CommonJS `require("./local")` occurrence: a call of * the required module's run-once %init at exactly this statement's * position — Node's inline evaluation, with the guard supplying the @@ -3830,6 +4076,7 @@ export class Lowerer { * their external dependencies by name). Null for everything else * (builtins load nothing; the rest kept its preflight fence). */ requireInitStmt(spec: string, node: ts.Node): IrStmt | null { + if (canonicalBuiltinModule(spec) === "punycode") return this.punycodeRequireWarning(node); // Project requires resolve within the program; another bare require can // be a program-module edge when it names an opted-in --npm-static package // (one package requiring another — the resolution answered its shipped @@ -3956,6 +4203,33 @@ export class Lowerer { } } + /** A file-scope get-or-create read (`let e = map[k]; if (e === + * undefined) ...` over an index-signature record) keeps a missing key as + * undefined (isGuardedRecordRead). Its pre-registered global takes the + * undefined arm before any body reads it; the declaration lowers its + * initializer as the matching absence probe. */ + analyzeGuardedRecordReadGlobals(parts: FileParts[]): void { + for (const { sf } of parts) { + for (const statement of sf.statements) { + if (!ts.isVariableStatement(statement)) continue; + for (const decl of statement.declarationList.declarations) { + if (!ts.isIdentifier(decl.name) || !isGuardedRecordRead(this, decl)) continue; + const global = this.globalOf(decl.name); + const symbol = this.resolveValueSymbol(decl.name); + if (!global || !symbol || global.tdz || this.runtimeOptionalBindingTypes.has(symbol)) + continue; + const widened = + global.type.kind === "union" + ? this.withUndefinedArmOf(global.type) + : this.withUndefinedArm(global.type); + if (!widened || typeEquals(widened, global.type)) continue; + global.type = widened; + this.runtimeOptionalBindingTypes.set(symbol, widened); + } + } + } + } + analyzeRuntimeOptionalArrayReads(parts: FileParts[]): void { const provenance = RuntimeOptionalProvenance.fromEnvironment(); // The site whose value the current promotion follows (debug only). @@ -3980,18 +4254,6 @@ export class Lowerer { if (!s) return null; return s.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(s) : s; }; - // Fields that can be read before their assignment hold undefined then; - // their reads are runtime-optional values like missing array reads. - for (const info of this.classes.values()) { - if (!info.earlyReadFields || !info.decl?.name) continue; - const classSymbol = symbolOf(info.decl.name); - if (!classSymbol) continue; - const instance = this.checker.getDeclaredTypeOfSymbol(classSymbol); - for (const name of info.earlyReadFields) { - const property = this.checker.getPropertyOfType(instance, name); - if (property) optionalSymbols.add(property); - } - } const signatureOf = (node: ts.Identifier): FnSig | null => { const symbol = symbolOf(node); if (!symbol) return null; @@ -6298,8 +6560,11 @@ export class Lowerer { for (const fp of parts) this.checker.prefetchSourceFile(fp.sf); } this.collectProgram(parts); - this.analyzeRuntimeOptionalArrayReads(parts); - this.analyzeDateJsonResults(parts); + this.speculativeTypeQueries(() => { + this.analyzeRuntimeOptionalArrayReads(parts); + this.analyzeDateJsonResults(parts); + this.analyzeGuardedRecordReadGlobals(parts); + }); // Decorated classes analyze AFTER the whole collection pass: a // decorator's return type may name a subclass declared below the // class, and reference lowering needs each class's rebindability @@ -6550,11 +6815,18 @@ export class Lowerer { }; if (module) sanitizeUnregisteredClassTypes(module, (name) => this.classes.has(name)); if (module) checkCycleBindingAccesses(this, module); + // Execution tiers from the final bodies (ir/tiers.ts). Assigned with or + // without a module: coverage reports the dynamic tier of programs that + // still have blockers. + this.dynamicFunctions = assignLegacyTiers(functions); + if (this.dynamicFunctions.length > 0) + this.dynamicBodies = functions.filter((fn) => fn.tier === "dynamic"); return { module, diagnostics: this.diags, runtimeFences: this.runtimeFences, stats: this.stats, + ...(this.dynamicFunctions.length > 0 ? { dynamicFunctions: this.dynamicFunctions } : {}), ...(this.statsByFile.size > 0 ? { statsByFile: this.statsByFile } : {}), ...(this.provenanceElided.length > 0 ? { provenanceElided: this.provenanceElided } : {}), }; @@ -6684,8 +6956,11 @@ export class Lowerer { collectionEdges.add(name); }; this.collectProgram(parts); - this.analyzeRuntimeOptionalArrayReads(parts); - this.analyzeDateJsonResults(parts); + this.speculativeTypeQueries(() => { + this.analyzeRuntimeOptionalArrayReads(parts); + this.analyzeDateJsonResults(parts); + this.analyzeGuardedRecordReadGlobals(parts); + }); // Decorated classes analyze post-collection here too: the %init seeds // lower the decoration calls, whose edges (decorator bodies, construct // thunks) reachable emit must see. @@ -7179,23 +7454,8 @@ export class Lowerer { } this.runtimeFences.push(...captured); - const first = captured[0]; + const fence = this.runtimeFenceStmt(captured[0], node, target.fallback, target.bareMessage); const loc = locOf(node); - const code = first?.code ?? target.fallback!.code; - const baseMessage = first?.message ?? target.fallback!.message; - const messageLoc = first?.loc ?? loc; - const source = first - ? (this.program.getSourceFile(messageLoc.file) ?? node.getSourceFile()) - : node.getSourceFile(); - const pos = ts.getLineAndCharacterOfPosition(source, messageLoc.start); - const fence: IrStmt = { - kind: "runtimeFence", - code, - message: target.bareMessage - ? baseMessage - : `${baseMessage} [${code} at ${messageLoc.file}:${pos.line + 1}]`, - loc, - }; if (target.kind === "statement") return fence; const params = target.params ?? []; @@ -7223,6 +7483,54 @@ export class Lowerer { return { kind: "closure", fnName: fn.name, captures: [], type: target.type, loc }; } + /** The runtimeFence statement that throws `first`'s message (or the + * fallback's) at `node`. */ + private runtimeFenceStmt( + first: ScrDiagnostic | undefined, + node: ts.Node, + fallback: { code: string; message: string } | undefined, + bareMessage = false, + ): IrStmt & { kind: "runtimeFence" } { + const loc = locOf(node); + const code = first?.code ?? fallback?.code ?? "SC1090"; + const baseMessage = + first?.message ?? + fallback?.message ?? + "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(); + const pos = ts.getLineAndCharacterOfPosition(source, messageLoc.start); + return { + kind: "runtimeFence", + code, + message: bareMessage + ? baseMessage + : `${baseMessage} [${code} at ${messageLoc.file}:${pos.line + 1}]`, + loc, + }; + } + + /** The current length of the capture buffer (diagSink), or 0 without one: + * the mark capturedRuntimeFence reads the refusal from. */ + diagSinkMark(): number { + return this.diagSink?.length ?? 0; + } + + /** The runtimeFence for a JavaScript statement refused while a capture + * buffer (diagSink) is active. The buffer's owner decides whether its + * diagnostics report, and some owners keep the lowered result (a + * successful tryLowerExpression candidate, a computed class base whose + * diagnostics defer to the class). Without a fence the refused statement + * would vanish from that result; with one it throws the refusal when it + * runs, exactly like the fence lowerStmts emits outside a buffer. The + * diagnostics stay in the buffer for its owner. Null without a buffer. */ + capturedRuntimeFence(sinkMark: number, node: ts.Node): IrStmt | null { + if (this.diagSink === null) return null; + return this.runtimeFenceStmt(this.diagSink[sinkMark], node, undefined); + } + /** All diagnostics land here; while a generic instance body is lowering, * the instantiation context is appended so the user knows which concrete * types made the (source-anchored) construct fail. */ @@ -7664,6 +7972,15 @@ export class Lowerer { : mapped; } + /** The runtime type of a record member slot typed `t`: an overloaded + * member's erased function (erasedOverloadMemberType — the type a record + * shape stores for it), otherwise mapTypeOf. */ + memberSlotTypeOf(t: ts.Type): IrType | null { + if (isOverloadedCallableMemberType(t, this.checker)) + return erasedOverloadMemberType(t, this.typeCtx) ?? null; + return this.mapTypeOf(t); + } + /** The one position where a contextual UNION must not be adopted over the * expression's own: the LEFT operand of `&&`, `||`, or `??`. Adopting a * contextual union is normally safe because tsc proved the value @@ -7837,6 +8154,8 @@ export class Lowerer { if (!mapped) { const argumentsType = jsArgumentsFunctionType(this, t); if (argumentsType !== null) return argumentsType; + const localClass = genericLocalClassBinding(this, node); + if (localClass !== null) return localClass; // The checked-dynamic declaration fallback (dynFallbackType): a // JAVASCRIPT binding of any inference residue, or a TypeScript // binding of genuine checker-`any`, becomes the checked-dynamic @@ -9837,6 +10156,43 @@ export class Lowerer { return lowerClassExpressionInfo(this, expr); } + /** Depth of the whole-program analyses that query types across every + * file before reachability is known (speculativeTypeQueries). */ + private speculativeTypeQueryDepth = 0; + + /** Runs a whole-program analysis whose type queries are speculative: they + * visit every file, reached or not, and no statement or declaration owns + * a refusal they trigger (collectLocalClassForType). */ + speculativeTypeQueries(analysis: () => void): void { + this.speculativeTypeQueryDepth++; + try { + analysis(); + } finally { + this.speculativeTypeQueryDepth--; + } + } + + /** Collects a function-local class because a type query named its + * instance type. Collection and body lowering own a refusal of that + * class: the poison reaches the declaration or statement that asked, as + * before. A speculative whole-program analysis owns none, so there the + * refusal stays reported (the build fails with it, as it did where such + * an analysis already caught the poison) and the class is unmappable for + * the query instead of the poison escaping the compiler. */ + collectLocalClassForType(decl: ts.ClassLikeDeclaration): boolean { + if (this.speculativeTypeQueryDepth === 0 || this.fnStack.length > 0) { + this.lowerClassExpressionInfo(decl); + return true; + } + try { + this.lowerClassExpressionInfo(decl); + return true; + } catch (e) { + if (e instanceof PoisonError) return false; + throw e; + } + } + lowerClassExpression(expr: ts.ClassLikeDeclaration): IrExpr { return lowerClassExpression(this, expr); } @@ -11165,6 +11521,7 @@ 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. + refuseWebAssemblyUse(this, expr.expression); return ( lowerLifetimeRegionCall(this, expr) ?? lowerInternTableCall(this, expr) ?? @@ -11390,6 +11747,7 @@ export class Lowerer { } lowerNew(expr: ts.NewExpression): IrExpr { + refuseWebAssemblyUse(this, expr.expression, "new "); const builtin = lowerLifetimeRegionNew(this, expr) ?? lowerInternTableNew(this, expr) ?? @@ -11641,7 +11999,23 @@ export class Lowerer { return { module: "timers/promises", member: `scheduler.${access.name.text}` }; } } + // punycode.ucs2.decode / encode: methods of the module's ucs2 object, + // dispatched as module members. + if (access.name.text === "decode" || access.name.text === "encode") { + const owner = access.expression; + const imported = ts.isIdentifier(owner) + ? this.builtinImportOf(owner) + : ts.isPropertyAccessExpression(owner) + ? this.builtinMemberOf(owner) + : null; + if (imported?.module === "punycode" && imported.member === "ucs2") { + return { module: "punycode", member: `ucs2.${access.name.text}` }; + } + } const module = this.builtinNamespaceModuleOf(access.expression); + // node:constants is one checked-dynamic object: its members read + // through the object itself (builtinConstantsObject), not the tables. + if (module === "constants") return null; if (module !== null) return { module, member: access.name.text }; // The events module object IS the EventEmitter class, so the class's // defaultMaxListeners is the module property. diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index d394c1960..44ed37e3c 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -1822,6 +1822,22 @@ export const BUILTIN_MODULE_FNS: Record< valueParams: [{ mode: "required", type: STRING }], }, }, + // node:punycode (deprecated): punycode.js 2.1.0's conversions over + // strings (scr_url.c). ucs2.decode/encode are members of the module's + // ucs2 object, dispatched as module members (builtinMemberOf). The + // DEP0040 report rides the module's load sites (punycodeLoadWarning). + punycode: { + decode: { fn: "punycode.decode", params: [STRING], result: STRING }, + encode: { fn: "punycode.encode", params: [STRING], result: STRING }, + toASCII: { fn: "punycode.toASCII", params: [STRING], result: STRING }, + toUnicode: { fn: "punycode.toUnicode", params: [STRING], result: STRING }, + "ucs2.decode": { fn: "punycode.ucs2Decode", params: [STRING], result: arrayOf(F64) }, + "ucs2.encode": { fn: "punycode.ucs2Encode", params: [arrayOf(F64)], result: STRING }, + }, + // node:constants (deprecated) is one frozen object (Object.assign of + // os.constants' groups, fs.constants and crypto.constants): bindings of + // the module read it as a checked-dynamic value (builtinConstantsObject). + constants: {}, // node:readline: createInterface's options are entirely special-cased // (exactly { input: process.stdin, output?: process.stdout } — see // lowerBuiltinModuleCall); the entry carries the canonical shape. The @@ -1951,6 +1967,7 @@ export const BUILTIN_MODULE_CONSTS: Record< os: { EOL: "\n", devNull: "/dev/null" }, worker_threads: { isMainThread: true, threadId: 0 }, cluster: { isPrimary: true, isMaster: true, isWorker: false }, + punycode: { version: "2.1.0" }, // zlib's module-level copies of its constants (every member outside // BROTLI_*, Node's own loop over zlib.constants). zlib: Object.fromEntries( @@ -3207,7 +3224,9 @@ export function isStdlibSymbol(lowerer: Lowerer, symbol: ts.Symbol | undefined): * `global.process`) — `declare var` globals ARE properties of * `typeof globalThis`, same symbol either way; * - a local alias binding (`const process = globalThis.process`, the - * tamper-guard prologue) registered in stdlibGlobalAliases. */ + * tamper-guard prologue) registered in stdlibGlobalAliases; + * - a const binding of the Map or Set constructor (`const M = Map`, + * builtinClassAliasOf). */ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): string | null { if (ts.isParenthesizedExpression(expr)) return stdlibGlobalNameOf(lowerer, expr.expression); if (ts.isIdentifier(expr)) { @@ -3225,6 +3244,8 @@ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): strin if (!symbol) return null; const alias = lowerer.stdlibGlobalAliases.get(symbol); if (alias !== undefined) return alias; + const classAlias = lowerer.builtinClassAliasOf(symbol); + if (classAlias?.kind === "collection") return classAlias.which; const decl = lowerer.checker.declarationsOf(symbol)[0]; if (decl !== undefined && ts.isImportClause(decl) && decl.name !== undefined) { const importDecl = decl.parent; diff --git a/packages/compiler/src/frontend/npm-static-prune.ts b/packages/compiler/src/frontend/npm-static-prune.ts index c6c1f9caf..b71d6ca33 100644 --- a/packages/compiler/src/frontend/npm-static-prune.ts +++ b/packages/compiler/src/frontend/npm-static-prune.ts @@ -10,12 +10,27 @@ import { nearestPackageType, resolveBareModule } from "./resolve.js"; import { npmStaticPackageOfPath } from "./npm-static.js"; import { trackedReadFile } from "./input-tracker.js"; import { npmPackageNameOf } from "./workspace-registry.js"; +import { typeOnlyNamedImport } from "./type-only-imports.js"; export function isPrunedNpmReexport(program: ts.Program, stmt: ts.ExportDeclaration): boolean { return program.analysis.prunedNpmReexports.has(stmt); } -function packageJson(path: string): Record | null { +type PackageJson = Record | null; + +/** Parsed package.json files by path, kept for one plan: every purity check + * reads the metadata of its file's package again. */ +type PackageJsonCache = Map; + +function packageJson(path: string, cache: PackageJsonCache): PackageJson { + const cached = cache.get(path); + if (cached !== undefined) return cached; + const json = readPackageJson(path); + cache.set(path, json); + return json; +} + +function readPackageJson(path: string): PackageJson { const source = trackedReadFile(path); if (source === null) return null; try { @@ -58,12 +73,16 @@ function sideEffectPattern(pattern: string): RegExp { /** A nested format scope such as dist/esm/package.json is not the package's * published metadata. Stay inside the resolved package while finding its * named root, including pnpm's realpath and workspace-linked installs. */ -function packageRootJsonPath(fromFile: string, packageName: string): string | null { +function packageRootJsonPath( + fromFile: string, + packageName: string, + cache: PackageJsonCache, +): string | null { let dir = dirname(resolve(fromFile)); let root: string | null = null; while (npmPackageNameOf(dir.replaceAll("\\", "/")) === packageName) { const path = join(dir, "package.json"); - if (packageJson(path)?.["name"] === packageName) root = path; + if (packageJson(path, cache)?.["name"] === packageName) root = path; const parent = dirname(dir); if (parent === dir) break; dir = parent; @@ -73,9 +92,13 @@ function packageRootJsonPath(fromFile: string, packageName: string): string | nu // An external package's executable sources may not be in this program. // Preserve the conservative whole-dependency promise in that case. -function pureExternalPackage(path: string, visiting = new Set()): boolean { +function pureExternalPackage( + path: string, + cache: PackageJsonCache, + visiting = new Set(), +): boolean { if (visiting.has(path)) return true; - const json = packageJson(path); + const json = packageJson(path, cache); const value = json?.["sideEffects"]; if (json === null || !(value === false || (Array.isArray(value) && value.length === 0))) return false; @@ -85,8 +108,8 @@ function pureExternalPackage(path: string, visiting = new Set()): boolea if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) continue; for (const name of Object.keys(dependencies)) { const resolved = resolveBareModule(path, name, "js-only"); - const root = resolved === null ? null : packageRootJsonPath(resolved.typesFile, name); - if (root === null || !pureExternalPackage(root, visiting)) return false; + const root = resolved === null ? null : packageRootJsonPath(resolved.typesFile, name, cache); + if (root === null || !pureExternalPackage(root, cache, visiting)) return false; } } return true; @@ -135,10 +158,11 @@ export function planNpmStaticReexports( for (const path of extraRoots) request(program.getSourceFile(path) ?? null, null); const pureMemo = new Map(); + const packageJsons: PackageJsonCache = new Map(); const filePure = (sf: ts.SourceFile): boolean => { const pkg = npmPackageNameOf(sf.fileName) ?? npmStaticPackageOfPath(sf.fileName); - const path = pkg === null ? null : packageRootJsonPath(sf.fileName, pkg); - const json = path === null ? null : packageJson(path); + const path = pkg === null ? null : packageRootJsonPath(sf.fileName, pkg, packageJsons); + const json = path === null ? null : packageJson(path, packageJsons); const value = json?.["sideEffects"]; if (value === false) return true; if ( @@ -195,8 +219,10 @@ export function planNpmStaticReexports( const resolved = resolveBareModule(sf.fileName, edge.spec, "js-only"); const pkg = resolved === null ? null : npmPackageNameOf(resolved.typesFile); const root = - pkg === null || resolved === null ? null : packageRootJsonPath(resolved.typesFile, pkg); - return root !== null && pureExternalPackage(root); + pkg === null || resolved === null + ? null + : packageRootJsonPath(resolved.typesFile, pkg, packageJsons); + return root !== null && pureExternalPackage(root, packageJsons); }); }; const pure = visit(root); @@ -232,7 +258,9 @@ export function planNpmStaticReexports( (clause.namedBindings !== undefined && (ts.isNamespaceImport(clause.namedBindings) || (ts.isNamedImports(clause.namedBindings) && - clause.namedBindings.elements.length === 0))) + clause.namedBindings.elements.length === 0))) || + // `import { type A }` still loads the module, like `import {}`. + typeOnlyNamedImport(stmt) ) { request(dep, null); } else { @@ -244,12 +272,10 @@ export function planNpmStaticReexports( } } } else if (ts.isExportDeclaration(stmt)) { + // `export { type A } from` still loads the module under Node's + // type stripping; only `export type … from` erases. if ( stmt.isTypeOnly || - (stmt.exportClause !== undefined && - ts.isNamedExports(stmt.exportClause) && - stmt.exportClause.elements.length > 0 && - stmt.exportClause.elements.every((element) => element.isTypeOnly)) || stmt.moduleSpecifier === undefined || !ts.isStringLiteral(stmt.moduleSpecifier) ) diff --git a/packages/compiler/src/frontend/npm-static.test.ts b/packages/compiler/src/frontend/npm-static.test.ts index a97b2f41c..0b4614e34 100644 --- a/packages/compiler/src/frontend/npm-static.test.ts +++ b/packages/compiler/src/frontend/npm-static.test.ts @@ -1,7 +1,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { expect, test } from "vitest"; -import { npmStaticIneligibleReason } from "./npm-static.js"; +import { npmStaticIneligibleReason, npmStaticTransformPkgJson } from "./npm-static.js"; test("an unreadable runtime entry is an eligibility refusal", async () => { const dir = await mkdtemp("/tmp/scriptc-npm-static-unreadable-"); @@ -14,3 +14,25 @@ test("an unreadable runtime entry is an eligibility refusal", async () => { await rm(dir, { recursive: true, force: true }); } }); + +test("a hoisted node condition leaves no types claim behind", () => { + // uuid 14: the node object carries its own "types" key. Left in place, + // tsgo maps the hidden declaration to its same-stem browser build. + const pkg: Record = { + types: "./dist/index.d.ts", + exports: { + ".": { + node: { types: "./dist/index.d.ts", default: "./dist-node/index.js" }, + default: "./dist/index.js", + }, + "./package.json": "./package.json", + }, + }; + npmStaticTransformPkgJson(pkg); + expect(pkg).toEqual({ + exports: { + ".": { default: "./dist-node/index.js" }, + "./package.json": "./package.json", + }, + }); +}); diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 1f83a12d4..3c8fa22e9 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -157,8 +157,6 @@ export function npmStaticTransformPkgJson(pkg: Record): void { return; } const obj = v as Record; - delete obj["types"]; - delete obj["typings"]; // 0. The "node" condition HOISTS: Node's runtime resolution matches it // (yaml's `{ types, node: "./dist/index.js", default: "./browser/ // index.js" }` runs the node dist; the browser dist is a DIFFERENT @@ -187,9 +185,21 @@ export function npmStaticTransformPkgJson(pkg: Record): void { } } } + // 1. The types claims go AFTER the hoist: a node object's own + // "types" key (uuid's `node: { types: "./dist/index.d.ts", default: + // "./dist-node/index.js" }`) splices in with its siblings, and left + // in place tsgo follows it, maps the hidden declaration to its + // same-stem JS (dist/index.js, the browser build) and disagrees with + // this resolver's runtime answer. + delete obj["types"]; + delete obj["typings"]; for (const value of Object.values(obj)) transform(value); }; if (pkg["exports"] !== undefined) transform(pkg["exports"]); + // The "imports" field maps "#" specifiers with the same condition objects + // (chalk 5's `"#supports-color": { node, default }`), so Node's "node" + // target hoists there too. + if (pkg["imports"] !== undefined) transform(pkg["imports"]); } /** npmStaticTransformPkgJson over a package.json TEXT (the fs shadow's @@ -204,6 +214,13 @@ function npmStaticTransformPkgJsonText(text: string): string { } if (doc === null || typeof doc !== "object" || Array.isArray(doc)) return text; npmStaticTransformPkgJson(doc as Record); + // tsgo deduplicates installs by package id (name + version): a second + // copy of the same name@version (express/node_modules/fresh beside + // send/node_modules/fresh) becomes a redirect to the first copy loaded, + // so its own path never joins the program. Node loads every installed + // copy as its own module, and resolve.ts answers each importer's own + // copy; without a version the checker keeps the copies apart too. + delete (doc as Record)["version"]; return JSON.stringify(doc); } @@ -271,6 +288,20 @@ function packageIsUntyped(path: string): boolean { return untyped; } +/** An installed TypeScript source with no runtime JavaScript at its stem + * (tldts ships `index.ts` beside `main: "dist/cjs/index.js"`). Node never + * executes TypeScript under node_modules and resolve.ts answers only + * runtime JS for opted-in packages, yet tsgo's TypeScript-first lookup + * would take the package-root `index.ts` over `main`. A same-stem JS twin + * keeps the source visible: that pair is the shape npmStaticProgramDep + * accepts in the JavaScript's place. */ +function isUnloadableInstalledTsSource(path: string): boolean { + if (!/\.(ts|tsx|mts|cts)$/.test(path) || isDeclarationFileName(path)) return false; + if (!isNodeModulesPathNorm(path)) return false; + const stem = path.replace(/\.(ts|tsx|mts|cts)$/, ""); + return ![".js", ".jsx", ".mjs", ".cjs"].some((ext) => trackedExists(stem + ext)); +} + export interface NpmStaticFsShadow { /** Shadowed CONTENT for a real path (the types-stripped package.json), * or undefined (no shadow — fall through). */ @@ -379,7 +410,7 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow const target = shadowTargetOf(path); if (!target) return false; if (target.viaTypes) return true; // the @types twin hides whole - return isDeclarationFileName(path); + return isDeclarationFileName(path) || isUnloadableInstalledTsSource(path); }, }; } diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 2ec9553f6..f7ab33429 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -25,11 +25,35 @@ import { isJsSourceFileName } from "./tsc-codes.js"; import { isRelativeSpecifier, packageNameOfSpecifier } from "./workspace-registry.js"; import { lowerToIr, type LowerOptions, type LowerResult } from "./lowering/lowerer.js"; import type { FrontendServices } from "./services.js"; -import { isImportDeclaration, isExportDeclaration, isStringLiteral } from "./ts7/adapter.js"; +import { + isIdentifier, + isImportDeclaration, + isExportDeclaration, + isStringLiteral, + isStringLiteralLike, + SyntaxKind, +} from "./ts7/adapter.js"; +import { moduleSourceCandidates } from "./module-source-candidates.js"; import { CONTRACT_TYPES_MODULE } from "./contract-types.js"; import { THREADS_MODULE } from "./threads-import.js"; +import { erasedTypeOnlyImport } from "./type-only-imports.js"; import { EXPERIMENTAL_MODULE } from "./experimental-import.js"; +/** Where a package-anchored preflight diagnostic sits, as + * `:` — the SC2013 refusal that drops the package + * reports only at the consumer's import, so its reason carries the inner + * location. */ +function packageDiagWhere(load: LoadResult, loc: SrcLoc): string { + const file = loc.file.split("\\").join("/"); + const at = file.lastIndexOf("/node_modules/"); + const shown = at >= 0 ? file.slice(at + "/node_modules/".length) : file; + const text = load.program.getSourceFile(loc.file)?.text; + if (text === undefined) return shown; + let line = 1; + for (let i = 0; i < loc.start && i < text.length; i++) if (text.charCodeAt(i) === 10) line++; + return `${shown}:${line}`; +} + export interface ProgramLoadOptions { npmStatic?: readonly string[]; externalTypes?: Readonly> | undefined; @@ -91,15 +115,36 @@ function detectAutoPackages( mode: "auto" | "lib" = "auto", judged?: Set, sites?: Map, + transitive: Set = new Set(), ): string[] { + const chosen: string[] = []; // package → the resolved types file AND the file whose import found it: // the runtime-JS probe below must resolve from the SAME importing file, // or a package visible only to a nested package.json realm (a pnpm // monorepo's packages/*/node_modules, unreachable from the entry's own // walk-up) answers "no runtime JS" for perfectly ordinary installs. - const seen = new Map(); - for (const sf of [...load.moduleOrder, load.entry]) { + const seen = new Map< + string, + { typesFile: string; fromFile: string; spec: string; owner: string | null } + >(); + // A transitive package's files outside the module order (reached through + // nested require() calls the graph walk does not model) still name its + // dependencies. + const scanned = new Set([...load.moduleOrder, load.entry]); + const transitiveFiles = + transitive.size === 0 + ? [] + : load.program.getImplementationSourceFiles().filter((sf) => { + const pkg = npmStaticPackageOfPath(sf.fileName); + return !scanned.has(sf) && pkg !== null && transitive.has(pkg); + }); + for (const sf of [...scanned, ...transitiveFiles]) { if (mode === "auto" && sf.fileName.includes("/node_modules/")) continue; + // `--npm-static=pkg...` opts pkg's whole dependency closure in: every + // package its compiled files import is selected the same explicit way, + // without the declaration eligibility bar. + const owner = npmStaticPackageOfPath(sf.fileName); + const viaTransitive = owner !== null && transitive.has(owner); const edges: { spec: string; loc: SrcLoc }[] = []; for (const stmt of sf.statements) { if ( @@ -107,13 +152,31 @@ function detectAutoPackages( stmt.moduleSpecifier && isStringLiteral(stmt.moduleSpecifier) ) { + // `import type` and `export type … from` erase under Node's type + // stripping: Node never loads the package, so it is no dependency + // to compile (its declarations still type the program). + if (isImportDeclaration(stmt) ? erasedTypeOnlyImport(stmt) : stmt.isTypeOnly) continue; edges.push({ spec: stmt.moduleSpecifier.text, loc: locOf(stmt) }); - } else if (mode === "lib") { + } else if (mode === "lib" && !viaTransitive) { // CJS packages spell their dep edges as top-level requires; the // import-statement scan alone would miss every one of them. for (const req of requiresOf(stmt)) edges.push({ spec: req.spec, loc: locOf(req.node) }); } } + // Transitive selection counts every literal require() and import() + // anywhere in the package, nested ones included. + if (viaTransitive) { + for (const call of moduleSourceCandidates(load.program, sf).calls) { + const arg = call.arguments[0]; + if ( + arg !== undefined && + isStringLiteralLike(arg) && + (call.expression.kind === SyntaxKind.ImportKeyword || + (isIdentifier(call.expression) && call.expression.text === "require")) + ) + edges.push({ spec: arg.text, loc: locOf(call) }); + } + } for (const { spec, loc } of edges) { if (load.externalTypes?.has(spec)) continue; // scriptc's own packages are not npm dependencies: contract types are @@ -137,28 +200,51 @@ function detectAutoPackages( if (judged?.has(packageNameOfSpecifier(spec))) continue; const npm = resolveNpmImport(sf.fileName, spec); if (npm !== null && isNodeTypesPath(npm.typesFile)) continue; - if (npm === null) { - if (mode !== "lib") continue; - const js = resolveBareModule(sf.fileName, spec, "js-only"); - if (js === null || judged!.has(js.packageName)) continue; - judged!.add(js.packageName); - sites!.set(js.packageName, loc); + // The runtime package Node loads. Types-first resolution answers an + // @types twin's declarations for a package that ships none, but + // selection and every refusal name the package that has the code. + const js = + mode === "lib" && (npm === null || npm.packageName.startsWith("@types/")) + ? resolveBareModule(sf.fileName, spec, "js-only") + : null; + const runtimePkg = js !== null && js.packageName !== npm?.packageName ? js.packageName : null; + if (npm === null || runtimePkg !== null) { + if (mode !== "lib" || runtimePkg === null || judged!.has(runtimePkg)) continue; + judged!.add(runtimePkg); + sites!.set(runtimePkg, loc); + if (viaTransitive) { + transitive.add(runtimePkg); + chosen.push(runtimePkg); + continue; + } statuses.push({ - package: js.packageName, + package: runtimePkg, status: "refused", - detail: "it ships no own .d.ts declaration surface", + 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`, + hint: npmStaticOptInHint(runtimePkg, owner), }); continue; } if (judged?.has(npm.packageName)) continue; + // An @types twin with no runtime package installed has nothing to + // compile (Node cannot load it either). + if (viaTransitive && !npm.packageName.startsWith("@types/")) { + judged!.add(npm.packageName); + sites!.set(npm.packageName, loc); + transitive.add(npm.packageName); + chosen.push(npm.packageName); + continue; + } if (!seen.has(npm.packageName)) { - seen.set(npm.packageName, { typesFile: npm.typesFile, fromFile: sf.fileName, spec }); + seen.set(npm.packageName, { typesFile: npm.typesFile, fromFile: sf.fileName, spec, owner }); sites?.set(npm.packageName, loc); } } } - const chosen: string[] = []; - for (const [pkg, { typesFile, fromFile, spec }] of seen) { + for (const [pkg, { typesFile, fromFile, spec, owner }] of seen) { judged?.add(pkg); // A package without a root entry (no "main", "exports" or index file) // can still serve the subpath the program imports. @@ -175,11 +261,23 @@ function detectAutoPackages( package: pkg, status: "refused", detail: mode === "lib" ? reason : `auto: ${reason}`, + // Naming a package bypasses the declaration and shipped-JS + // heuristics; it cannot supply JavaScript that does not resolve. + ...(mode === "lib" && jsEntry !== null ? { hint: npmStaticOptInHint(pkg, owner) } : {}), }); } return chosen; } +/** How to compile a package the declaration bar refused anyway: name it, + * or (for a dependency of an opted-in package) opt in that package's + * whole dependency closure. */ +function npmStaticOptInHint(pkg: string, importer: string | null): string { + return importer === null + ? `to compile it anyway, name it with --npm-static=${pkg} (or --npm-static=${pkg}... to include its dependencies)` + : `to compile it anyway, name it with --npm-static=${pkg}, or use --npm-static=${importer}... to include all of ${importer}'s dependencies`; +} + /** The opted-in packages a consumer-anchored tsc message NAMES: module * specifiers in `Module '"spec"'` phrasings, and resolved file paths in * `import("…")` type spellings — the two ways the checker points at an @@ -227,9 +325,23 @@ export function runFrontend( // selections bypass eligibility heuristics, but never compilation checks. const explicit: readonly string[] = npmStatic !== undefined && typeof npmStatic !== "string" ? npmStatic : []; - return loadFrontend(entryPath, trackedLoader, "lib", externalTypes, [ - ...new Set([...libraryNpmStatic, ...explicit]), - ]); + // A trailing "..." (`--npm-static=fs-extra...`) selects the package and + // its whole dependency closure, which the library fixpoint discovers. + const transitive = new Set(); + const names = [...libraryNpmStatic, ...explicit].map((name) => { + if (!name.endsWith("...") || name.length === 3) return name; + const base = name.slice(0, -3); + transitive.add(base); + return base; + }); + return loadFrontend( + entryPath, + trackedLoader, + "lib", + externalTypes, + [...new Set(names)], + transitive, + ); } catch (error) { for (const load of active) { // Preserve the original failure and attempt every remaining cleanup. @@ -249,6 +361,7 @@ function loadFrontend( npmStatic?: readonly string[] | "auto" | "lib", externalTypes?: Readonly>, libraryNpmStatic: readonly string[] = [], + transitive: Set = new Set(), ): Frontend { // Resolver package/workspace metadata is intentionally shared across the // several load attempts of ONE auto-detection fixpoint, but never across @@ -270,7 +383,10 @@ function loadFrontend( const scoutPreflight = checkPreflight(scout); requested = npmStatic === "lib" - ? [...libraryNpmStatic, ...detectAutoPackages(scout, statuses, "lib", judged, npmSites)] + ? [ + ...libraryNpmStatic, + ...detectAutoPackages(scout, statuses, "lib", judged, npmSites, transitive), + ] : detectAutoPackages(scout, statuses); // With no package to opt in, the scout already IS the final frontend: // same roots, resolution posture, preflight, and module order. Retain it @@ -328,7 +444,7 @@ function loadFrontend( // at least one new package for good). if (npmStatic === "lib") { for (;;) { - const grown = detectAutoPackages(load, statuses, "lib", judged, npmSites); + const grown = detectAutoPackages(load, statuses, "lib", judged, npmSites, transitive); if (grown.length === 0) break; requested = [...requested, ...grown]; load.dispose(); @@ -344,7 +460,8 @@ function loadFrontend( const reasons = new Map(npmStaticOffenders()); for (const d of preflight) { const pkg = npmStaticPackageOfPath(d.loc.file); - if (pkg !== null && !reasons.has(pkg)) reasons.set(pkg, `${d.code}: ${d.message}`); + if (pkg !== null && !reasons.has(pkg)) + reasons.set(pkg, `${d.code} at ${packageDiagWhere(load, d.loc)}: ${d.message}`); } if (![...reasons.keys()].some((p) => effective.has(p))) { const named = new Map(); @@ -476,6 +593,7 @@ function loadFrontend( ...opts, frontendServices: finalLoad.services, startupCrash: finalLoad.startupCrash ?? null, + inlineRequireCycleCalls: finalLoad.inlineRequireCycleCalls, projectCheckJs: finalLoad.typeWorld?.projectCheckJs ?? false, externalTypes: finalLoad.externalTypes, externalTypeSpecifiersByFile: finalLoad.externalTypeSpecifiersByFile, diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index df0cb673e..a4ab30105 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -119,6 +119,11 @@ import { import { trackedFileExists, trackedReadFile, trackedRealpath } from "./input-tracker.js"; import { forkTargetPaths } from "./fork-target.js"; import { workerTargetPaths } from "./worker-target.js"; +import { + erasedTypeOnlyImport, + typeOnlyNamedImport, + typeOnlyReachedFiles, +} from "./type-only-imports.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js"; @@ -333,6 +338,12 @@ export interface LoadResult { * last. Empty until checkPreflight runs (and empty when preflight fails * structurally). */ moduleOrder: ts.SourceFile[]; + /** Inline require() calls whose edge closes a require cycle. Node answers + * them with the still-evaluating module's partial exports, which the + * static export globals do not model; their lowering refuses them by name + * at run time instead of refusing the whole program. Filled by + * checkPreflight. */ + inlineRequireCycleCalls?: ReadonlySet; /** Node's startup refusal, when the graph carries an import edge Node * rejects before ANY module evaluates — a RESOLUTION refusal (a bare * specifier nothing installed resolves, an exports/imports map that @@ -635,9 +646,14 @@ export function entryPackageFilePredicate(entryPath: string): (file: string) => } /** maxNodeModuleJsDepth can omit relative imports within an admitted - * package. Discover its runtime sources to a fixpoint so the checker and - * module resolver see the same graph regardless of import traversal order. */ -function entryPackageProgramRoots7(program: ts.Program, entryPath: string): string[] { + * package, and opted-in packages deep in the dependency graph. Discover + * their runtime sources to a fixpoint so the checker and module resolver + * see the same graph regardless of import traversal order or depth. */ +function entryPackageProgramRoots7( + program: ts.Program, + entryPath: string, + rootNpmStaticEdges: boolean, +): string[] { const belongsToEntry = entryPackageFilePredicate(entryPath); const roots = new Set(); for (const sf of program.getImplementationSourceFiles()) { @@ -656,15 +672,63 @@ function entryPackageProgramRoots7(program: ts.Program, entryPath: string): stri program.getSourceFile(target) === undefined ) roots.add(target); + // An opted-in package's bare edge counts against the same depth + // budget: tsgo charges every node_modules JS hop, so express's + // body-parser → qs → side-channel → get-intrinsic chain runs out of + // depth before its leaves load. Root the runtime file this edge + // lowers to (preflight's own resolution and miss rule) so the leaf + // joins like a shallow one; complete programs add nothing. Only + // the shadowed lowering world does this (the authored world checks + // user code against declarations), and only from package files: a + // program file's own edges are never deeper than one hop. + // (resolveBareModule directly: resolveNpmImport7's workspace + // registration stays with preflight's own walk.) + if ( + target === null && + rootNpmStaticEdges && + npmStaticPackageOfPath(sf.fileName) !== null && + !isRelativeSpecifier(specifier.text) + ) { + const spec = specifier.text; + if (spec.startsWith("#") || canonicalBuiltinModule(spec) !== null) continue; + const npm = resolveBareModule( + sf.fileName, + spec, + undefined, + moduleSpecifierKind7(specifier), + ); + if ( + npm !== null && + isNpmStaticPackage(npm.packageName) && + (isNodeModulesPath(npm.typesFile) || npm.workspaceDir !== undefined) && + isJsSourceFileName(npm.typesFile) && + npmStaticProgramFile(program, npm.typesFile) === null + ) + roots.add(tsgoPath(resolve(npm.typesFile))); + } } } return [...roots].sort(); } +/** "require" for a CommonJS edge (`require("x")`, `import x = require("x")`), + * "import" for every ESM form — the resolution kind preflight uses. */ +function moduleSpecifierKind7(specifier: ts.StringLiteralLike): "import" | "require" { + const parent = specifier.parent; + if (ts.isExternalModuleReference(parent)) return "require"; + return ts.isCallExpression(parent) && + ts.isIdentifier(parent.expression) && + parent.expression.text === "require" + ? "require" + : "import"; +} + function loadProgram7( host: ts.Ts7Host, entryPath: string, externalTypes: ReadonlyMap = new Map(), + /** The --npm-static lowering world: root opted-in packages' deep edges. */ + rootNpmStaticEdges = false, ): LoadResult & { disposeAll: () => void } { const config = adoptProjectConfig7(host, entryPath); const configuredPaths = config.options["paths"]; @@ -712,7 +776,7 @@ function loadProgram7( try { for (let pass = 0; pass < 32; pass++) { const candidates = [ - ...entryPackageProgramRoots7(program, entryPath), + ...entryPackageProgramRoots7(program, entryPath, rootNpmStaticEdges), ...createRequireProgramRoots7(program), ...forkTargetPaths(program, program.getImplementationSourceFiles()), ...workerTargetPaths(program, program.getImplementationSourceFiles()), @@ -926,7 +990,7 @@ export function loadProgram( }; const host = services.createProgramHost({ cwd: dirname(entryPath), fsShadow }); try { - const load = loadProgram7(host, entryPath, externalTypes); + const load = loadProgram7(host, entryPath, externalTypes, npmStaticPackages.length > 0); let authoredHost: ts.Ts7Host | null = null; let authoredLoad: ReturnType | null = null; return { @@ -970,9 +1034,10 @@ export function loadProgram( * preflight diagnostics. Preserve the caller's complete load shape when * mutating it, including its owned services and disposal callback. */ export function checkPreflight(load: T): ScrDiagnostic[] { - const { diags, moduleOrder, startupCrash } = preflight7(load); + const { diags, moduleOrder, startupCrash, inlineRequireCycleCalls } = preflight7(load); load.moduleOrder = moduleOrder; load.startupCrash = startupCrash; + load.inlineRequireCycleCalls = inlineRequireCycleCalls; return diags; } @@ -1837,6 +1902,36 @@ function nestedBareRequiresOf7(sf: ts.SourceFile): ts.CallExpression[] { return out; } +/** INLINE `require("...")` calls in expression position anywhere in a file + * (`require("./x").f`, `require("debug")("ns")`, `module.exports = + * require("./y")`, `{ v: require("./v") }`): each evaluates its module at + * that position, exactly like a bare require statement. Top-level require + * statements and bare require statements are collected by their own + * scans; a require that initializes a nested declaration keeps its + * lowering fence and is not an edge. */ +function inlineRequiresOf7(sf: ts.SourceFile): ts.CallExpression[] { + const out: ts.CallExpression[] = []; + ts.walkPreorder(sf, (n) => { + if (!ts.isCallExpression(n) || requireSpecOf7(n) === null) return; + let outer: ts.Node = n; + while ( + outer.parent !== undefined && + (ts.isParenthesizedExpression(outer.parent) || + ts.isAsExpression(outer.parent) || + ts.isTypeAssertion(outer.parent) || + ts.isNonNullExpression(outer.parent)) + ) { + outer = outer.parent; + } + const parent = outer.parent; + if (parent !== undefined && ts.isExpressionStatement(parent)) return; + if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.initializer === outer) + return; + out.push(n); + }); + return out; +} + type RequireCallBindingKind7 = "ambient" | "createRequire" | "source"; function stripRequireCasts7(expr: ts.Expression): ts.Expression { @@ -2146,7 +2241,7 @@ function insideFunctionLike7(node: ts.Node): boolean { function nodeEsmSyntaxMarker7(sf: ts.SourceFile): ts.Node | null { for (const stmt of sf.statements) { if (ts.isImportDeclaration(stmt) && !erasedTypeOnlyImport(stmt)) return stmt; - if (ts.isExportDeclaration(stmt) && !stmt.isTypeOnly && !erasedTypeOnlyReexport(stmt)) { + if (ts.isExportDeclaration(stmt) && !stmt.isTypeOnly) { return stmt; } if (ts.isExportAssignment(stmt)) return stmt; @@ -2403,6 +2498,13 @@ function nsBindingUsesAreBareStatements7( export interface CycleEdge { dep: ts.SourceFile; stmt?: ts.ImportDeclaration | ts.ExportDeclaration; + /** An inline require() in a deferred position (a function body): Node + * evaluates the dependency when that code runs, after this module's own + * top level, so the edge orders the dependency after the module and + * never closes a cycle through the module's initialization. */ + lazy?: true; + /** The inline require() call behind the edge (inline edges only). */ + call?: ts.CallExpression; } /** True when the reference at `node` can only evaluate AFTER the module @@ -2829,6 +2931,7 @@ export function makeCycleAdmission( const clause = e.stmt.importClause; if ( clause === undefined || + typeOnlyNamedImport(e.stmt) || (clause.name === undefined && (clause.namedBindings === undefined || (ts.isNamespaceImport(clause.namedBindings) && @@ -4000,6 +4103,24 @@ function locOf7(node: ts.Node): { file: string; start: number; end: number } { return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; } +/** The program file holding an opted-in package's resolved runtime JS: + * the file itself, or its same-stem TypeScript twin. */ +function npmStaticProgramFile(program: ts.Program, resolvedFile: string): ts.SourceFile | null { + let dep = program.getSourceFile(resolvedFile) ?? null; + if (dep === null) { + // A workspace-linked package shipping its .ts SOURCE beside the + // compiled .js: the checker's resolution preferred the source (it + // outranks the sibling .js under bundler resolution), so the program + // holds the .ts twin at the same stem — the same module, in its + // richer spelling. + for (const ext of [".ts", ".tsx", ".mts", ".cts"]) { + dep = program.getSourceFile(resolvedFile.replace(/\.(js|mjs|cjs)$/, ext)) ?? null; + if (dep !== null) break; + } + } + return dep; +} + /** The PROGRAM source file an opted-in --npm-static package's resolved * entry maps to, or null with the package marked an offender (resolution * still answering declarations, or the file missing from the type-checked @@ -4016,18 +4137,7 @@ function npmStaticProgramDep( ); return null; } - let dep = program.getSourceFile(resolvedFile) ?? null; - if (dep === null) { - // A workspace-linked package shipping its .ts SOURCE beside the - // compiled .js: the checker's resolution preferred the source (it - // outranks the sibling .js under bundler resolution), so the program - // holds the .ts twin at the same stem — the same module, in its - // richer spelling. - for (const ext of [".ts", ".tsx", ".mts", ".cts"]) { - dep = program.getSourceFile(resolvedFile.replace(/\.(js|mjs|cjs)$/, ext)) ?? null; - if (dep !== null) break; - } - } + const dep = npmStaticProgramFile(program, resolvedFile); if (dep === null) { reportNpmStaticOffender( packageName, @@ -4079,6 +4189,7 @@ function preflight7(load: LoadResult): { diags: ScrDiagnostic[]; moduleOrder: ts.SourceFile[]; startupCrash: StartupCrash | null; + inlineRequireCycleCalls: ReadonlySet; } { const { program, entry } = load; const diags: ScrDiagnostic[] = [...load.configDiags]; @@ -4299,21 +4410,41 @@ function preflight7(load: LoadResult): { npmStaticPackageOfPath(sf.fileName) !== null) && !unselectedPackageJsFile(sf.fileName), ); + // Files reached only through `import type` / `export type … from` are + // checker input, never program modules: Node does not load them, so + // their imports and syntax must not fence the program. + const resolveRuntimeEdge = ( + sf: ts.SourceFile, + spec: string, + resolutionKind?: "import" | "require", + ): ts.SourceFile | null => + resolveImport7(program, sf, spec, resolutionKind) ?? + npmStaticDepSf7(program, sf, spec, resolutionKind); + const typeOnlyFiles = typeOnlyReachedFiles( + program, + entry, + programFiles, + () => [ + ...forkTargetPaths(program, program.getImplementationSourceFiles()), + ...workerTargetPaths(program, program.getImplementationSourceFiles()), + ], + resolveRuntimeEdge, + ); + const runtimeFiles = + typeOnlyFiles.size === 0 ? programFiles : programFiles.filter((sf) => !typeOnlyFiles.has(sf)); const userFiles = npmStaticActive() ? planNpmStaticReexports( program, entry, - programFiles, + runtimeFiles, [ ...createRequireProgramRoots7(program), ...forkTargetPaths(program, program.getImplementationSourceFiles()), ...workerTargetPaths(program, program.getImplementationSourceFiles()), ], - (sf, spec, resolutionKind) => - resolveImport7(program, sf, spec, resolutionKind) ?? - npmStaticDepSf7(program, sf, spec, resolutionKind), + resolveRuntimeEdge, ) - : programFiles; + : runtimeFiles; program.getTypeChecker().prefetchSourceFileStructures(userFiles); // Unselected node_modules JS is not native program source even when @@ -4438,8 +4569,7 @@ function preflight7(load: LoadResult): { continue; } if (ts.isExportDeclaration(stmt)) { - if (stmt.isTypeOnly || erasedTypeOnlyReexport(stmt) || isPrunedNpmReexport(program, stmt)) - continue; + if (stmt.isTypeOnly || isPrunedNpmReexport(program, stmt)) continue; if (!stmt.moduleSpecifier) continue; const fromSpec = ts.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : ""; if (load.externalTypes.has(fromSpec)) { @@ -4860,6 +4990,21 @@ function preflight7(load: LoadResult): { pattern.elements.every((e) => { if (e.initializer !== undefined || e.dotDotDotToken !== undefined) return false; if (e.name !== undefined && ts.isIdentifier(e.name)) return true; + // A nested pattern under a named export of a program module + // destructures that export's VALUE at the require + // (`const { codes: { ERR_X } } = require("./errors")`): the + // inner bindings are ordinary destructured storage. + if ( + module === null && + builtin === null && + bindingKind !== "createRequire" && + pattern === req.decl!.name && + e.name !== undefined && + ts.isObjectBindingPattern(e.name) && + e.propertyName !== undefined && + ts.isIdentifier(e.propertyName) + ) + return true; if (module === null || e.name === undefined || !ts.isObjectBindingPattern(e.name)) return false; const key = e.propertyName; @@ -4973,6 +5118,32 @@ function preflight7(load: LoadResult): { continue; } } + // Inline requires of program modules are module edges like the bare + // statements above (cjs-inline-require.ts lowers each to the module's + // %init at its position). Only the CommonJS global in a CommonJS file + // counts, and targets outside the compiled graph add nothing here: + // their use sites keep their existing lowering fences. + if (jsRequireFile && !isNodeEsmFile7(sf, program)) { + for (const call of inlineRequiresOf7(sf)) { + if (requireCallBindingKind7(program, call) !== "ambient") continue; + const spec = requireSpecOf7(call)!; + if (load.externalTypes.has(spec) || canonicalBuiltinModule(spec) !== null) continue; + const lazy = inDeferredPosition7(call); + const edgeTo = (dep: ts.SourceFile): CycleEdge => + lazy ? { dep, lazy: true, call } : { dep, call }; + const projectDep = resolveImport7(program, sf, spec, "require"); + if (projectDep !== null) { + if (!projectDep.fileName.endsWith(".json")) deps.push(edgeTo(projectDep)); + continue; + } + if (isRelativeSpecifier(spec) || spec.startsWith("#")) continue; + const npmReq = resolveNpmImport7(sf.fileName, spec, "require"); + if (npmReq !== null && isNpmStaticPackage(npmReq.packageName)) { + const nDep = npmStaticProgramDep(program, npmReq.packageName, npmReq.typesFile); + if (nDep !== null) deps.push(edgeTo(nDep)); + } + } + } } } @@ -4999,8 +5170,12 @@ function preflight7(load: LoadResult): { // reads are all let/const bindings with module storage — the // lowering checks each one (cycleEarlyBindings) and throws Node's // ReferenceError on a read before the declaration ran. - const cycleAdmissionReason = makeCycleAdmission(program, (sf) => edges.get(sf) ?? []); + const cycleAdmissionReason = makeCycleAdmission(program, (sf) => { + const all = edges.get(sf) ?? []; + return all.some((e) => e.call !== undefined) ? all.filter((e) => e.call === undefined) : all; + }); const order: ts.SourceFile[] = []; + const inlineRequireCycleCalls = new Set(); const state = new Map(); const stack: string[] = []; const visit = (sf: ts.SourceFile): void => { @@ -5008,9 +5183,14 @@ function preflight7(load: LoadResult): { stack.push(sf.fileName); for (const e of edges.get(sf) ?? []) { if (e.dep === sf) continue; // self-import: a Node cache hit, not a cycle + if (e.lazy === true) continue; // visited after this module completes const s = state.get(e.dep); if (s === "done") continue; if (s === "visiting") { + if (e.call !== undefined) { + inlineRequireCycleCalls.add(e.call); + continue; + } // A back-edge: Node answers it from the cache (the module is // already evaluating) — admitted by the cheap per-edge rule, the // declaration-only init window rule, or the exact initialization @@ -5034,6 +5214,11 @@ function preflight7(load: LoadResult): { stack.pop(); state.set(sf, "done"); order.push(sf); + // Lazy inline requires run after this module's top level: their + // modules join the order afterwards (cache hits when already loaded). + for (const e of edges.get(sf) ?? []) { + if (e.lazy === true && state.get(e.dep) === undefined) visit(e.dep); + } }; visit(entry); @@ -5087,7 +5272,12 @@ function preflight7(load: LoadResult): { esmNamedImportLinkCheck(program, entry, order, diags), ); - return { diags, moduleOrder: order, startupCrash: resolveCrash ?? linkCrash }; + return { + diags, + moduleOrder: order, + startupCrash: resolveCrash ?? linkCrash, + inlineRequireCycleCalls, + }; } /* ── the CJS named-import link check ───────────────────────────────────── @@ -5708,42 +5898,6 @@ export function isCheckedJsSourceFile(sf: ts.SourceFile, projectCheckJs: boolean return checked; } -/** `import type …` declarations and named clauses whose every element is - * `type`-qualified are ERASED by tsc at emit — Node's module graph has no - * edge there, so module order, cycle detection, init calls, and the - * startup-refusal probe must not count them either (zod v4's core modules - * are "cyclic" only through `import type * as ns` namespaces — 27 phantom - * SC1016 sites against 1 real value-level cycle; and a type-only import - * of a module Node cannot resolve is NOT a startup crash — Node never - * resolves it). Originated as the provenance-sources pilot's flag-gated - * prototype (SCRIPTC_TYPE_ONLY_EDGES), promoted to always-on with the - * full-suite differential green. Side-effect imports (no clause) stay - * real edges. */ -function erasedTypeOnlyImport(stmt: ts.ImportDeclaration): boolean { - const clause = stmt.importClause; - if (clause === undefined) return false; // side-effect import: a real edge - if (clause.phaseModifier === ts.SyntaxKind.TypeKeyword) return true; - return ( - clause.name === undefined && - clause.namedBindings !== undefined && - ts.isNamedImports(clause.namedBindings) && - clause.namedBindings.elements.length > 0 && - clause.namedBindings.elements.every((e) => e.isTypeOnly) - ); -} - -/** The re-export twin: `export { type X } from "./m"` with every element - * `type`-qualified erases at emit exactly like `export type { X } from` - * (which stmt.isTypeOnly already covers) — no module edge. */ -function erasedTypeOnlyReexport(stmt: ts.ExportDeclaration): boolean { - return ( - stmt.exportClause !== undefined && - ts.isNamedExports(stmt.exportClause) && - stmt.exportClause.elements.length > 0 && - stmt.exportClause.elements.every((e) => e.isTypeOnly) - ); -} - export function orderedImportsOf( program: ts.Program, sf: ts.SourceFile, @@ -5751,11 +5905,7 @@ export function orderedImportsOf( const out: { stmt: ts.Statement; dep: ts.SourceFile | null }[] = []; for (const stmt of sf.statements) { if (ts.isExportDeclaration(stmt) && isPrunedNpmReexport(program, stmt)) continue; - if ( - ts.isExportDeclaration(stmt) && - (stmt.isTypeOnly || erasedTypeOnlyReexport(stmt) || !stmt.moduleSpecifier) - ) - continue; + if (ts.isExportDeclaration(stmt) && (stmt.isTypeOnly || !stmt.moduleSpecifier)) continue; if (!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt)) continue; if (ts.isImportDeclaration(stmt) && erasedTypeOnlyImport(stmt)) continue; if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier)) continue; @@ -5830,6 +5980,37 @@ export function cjsExportAssignmentOf( return null; } +/** The members and shared value of a CHAINED member export + * `exports.a = exports.b = module.exports.c = value` (outermost name + * first), or null when the assignment is not a chain of at least two + * identifier-named export members or its value is undefined. */ +export function cjsExportChainOf( + e: ts.BinaryExpression, +): { names: ts.Identifier[]; value: ts.Expression } | null { + const names: ts.Identifier[] = []; + let cur: ts.Expression = e; + for (;;) { + while (ts.isParenthesizedExpression(cur)) cur = cur.expression; + if (!ts.isBinaryExpression(cur) || cur.operatorToken.kind !== ts.SyntaxKind.EqualsToken) break; + const left = cur.left; + if ( + !ts.isPropertyAccessExpression(left) || + left.questionDotToken || + !ts.isIdentifier(left.name) + ) + break; + const recv = left.expression; + if (!((ts.isIdentifier(recv) && recv.text === "exports") || isModuleExportsAccess(recv))) break; + names.push(left.name); + cur = cur.right; + } + // tsc's `exports.a = exports.b = void 0;` preamble is not a value chain + // (the export-statement lowering owns it). + const isVoid = + cur.kind === ts.SyntaxKind.VoidExpression || (ts.isIdentifier(cur) && cur.text === "undefined"); + return names.length >= 2 && !isVoid ? { names, value: cur } : null; +} + /** Why Node would DISCARD this CommonJS export statement, or null when it * survives (Node's export-object identity rules — checker-free, pure * AST): `module.exports = {...}` REPLACES the export object, so members diff --git a/packages/compiler/src/frontend/resolve.test.ts b/packages/compiler/src/frontend/resolve.test.ts index 0d135b3c2..0dd09e73d 100644 --- a/packages/compiler/src/frontend/resolve.test.ts +++ b/packages/compiler/src/frontend/resolve.test.ts @@ -11,6 +11,7 @@ import { setProjectPathMappings, setProjectRealm, } from "./resolve.js"; +import { setNpmStaticPackages } from "./npm-static.js"; const fixturesRoot = join(import.meta.dirname, "../../../..", "tests/fixtures"); @@ -60,3 +61,56 @@ test("source-only workspace packages are project modules while JavaScript packag clearResolveCaches(); } }); + +test("package imports inside a --npm-static package answer the shipped JavaScript", async () => { + // chalk 5's shape: a declaration twin beside every runtime file, and a + // `{ node, default }` imports entry whose node target is what Node runs. + const dir = realpathSync(await mkdtemp(join(tmpdir(), "scriptc-resolve-imports-"))); + try { + const pkg = join(dir, "node_modules", "colors"); + const vendor = join(pkg, "source", "vendor"); + await mkdir(join(vendor, "styles"), { recursive: true }); + await mkdir(join(vendor, "detect"), { recursive: true }); + await Promise.all([ + writeFile( + join(pkg, "package.json"), + JSON.stringify({ + name: "colors", + version: "1.0.0", + type: "module", + exports: "./source/index.js", + imports: { + "#styles": "./source/vendor/styles/index.js", + "#detect": { + node: "./source/vendor/detect/index.js", + default: "./source/vendor/detect/browser.js", + }, + }, + }), + ), + writeFile(join(pkg, "source", "index.js"), 'import styles from "#styles";\n'), + writeFile(join(pkg, "source", "index.d.ts"), "export {};\n"), + writeFile(join(vendor, "styles", "index.js"), "export default {};\n"), + writeFile(join(vendor, "styles", "index.d.ts"), "export {};\n"), + writeFile(join(vendor, "detect", "index.js"), "export default 1;\n"), + writeFile(join(vendor, "detect", "index.d.ts"), "export {};\n"), + writeFile(join(vendor, "detect", "browser.js"), "export default 0;\n"), + writeFile(join(vendor, "detect", "browser.d.ts"), "export {};\n"), + ]); + const from = join(pkg, "source", "index.js"); + + clearResolveCaches(); + setNpmStaticPackages([]); + expect(resolveProjectModule(from, "#styles")).toBe(join(vendor, "styles", "index.d.ts")); + + clearResolveCaches(); + setNpmStaticPackages(["colors"]); + expect(resolveProjectModule(from, "#styles")).toBe(join(vendor, "styles", "index.js")); + expect(resolveProjectModule(from, "#detect")).toBe(join(vendor, "detect", "index.js")); + expect(resolveProjectModule(from, "#missing")).toBeNull(); + } finally { + setNpmStaticPackages([]); + clearResolveCaches(); + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index db440c199..463400475 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -617,6 +617,18 @@ export function resolveProjectImport(fromFile: string, specifier: string): strin // Node's validity rule: only the bare "#" is never resolvable ("#/..." // matches "#/*"-pattern keys on current Node). if (specifier === "#") return null; + // Inside an opted-in --npm-static package, an imports entry resolves + // the way the tsgo host's fs shadow does: the transformed document + // (the "node" condition hoisted), the runtime JS conditions, and the + // shipped JS rather than a declaration twin (see resolveRelativeModule). + if (npmStaticPackageOfPath(resolve(fromFile)) !== null) { + const transformed = JSON.parse(JSON.stringify(pkg)) as PkgJson & Record; + npmStaticTransformPkgJson(transformed); + const jsTarget = resolvePackageImports(transformed.imports, specifier, JS_ONLY_CONDITIONS); + if (jsTarget === null || !jsTarget.startsWith("./")) return null; + const jsPath = join(pkgDir, jsTarget); + return normalizeResolvedAnswer(loadAsJsFile(jsPath) ?? loadAsJsDirectory(jsPath)); + } target = resolvePackageImports(pkg.imports, specifier, EXPORT_CONDITIONS); // An imports target that is not "./"-relative is PACKAGE_RESOLVE'd as // a bare specifier (Node's PACKAGE_IMPORTS_EXPORTS_RESOLVE): the diff --git a/packages/compiler/src/frontend/tier-analysis.ts b/packages/compiler/src/frontend/tier-analysis.ts new file mode 100644 index 000000000..ded7c5225 --- /dev/null +++ b/packages/compiler/src/frontend/tier-analysis.ts @@ -0,0 +1,629 @@ +/* Tier assignment, report-only (two-tier design, stage S2). + * + * The design assigns every binding, field, function instance, class and + * module a tier. Static is today's compiler; dynamic code operates on + * dynamic values. Demotion from static to dynamic is monotone and is seeded + * by `any`, untyped JavaScript, types and operations without a static + * lowering, reflection a static layout cannot answer, and dynamic modules + * (DESIGN.md §6). + * + * Stage S2 computes what WOULD be demoted without demoting anything: + * + * 1. Existing dynamic code. Functions whose bodies already use the `dyn` + * vocabulary are the dynamic tier today (ir/tiers.ts assigns them). The + * source statements whose lowering performs a dynamic operation are the + * coverage report's statementsDynamic. + * 2. Demotion requests. Every refusal or runtime fence the lowerer commits + * (today's poison sites: the SC1xxx/SC2xxx families) and every refused + * import or package becomes a request, attributed to the unit whose + * static representation could not express the construct. The lowerer + * still refuses or fences exactly as before; the requests are recorded + * from the committed diagnostics, so speculative probes that roll back + * record nothing. Under `--tier=static-only` every request stays today's + * refusal; stage S6 turns requests into demotions in the default mode. + * 3. Demoted units. A request demotes its unit, and the function instance + * containing the site turns dynamic (it would hold dynamic operations, + * invariant I1). Units are deduplicated; each keeps its requests, which + * is the chain `--explain-dynamic` prints. + * + * Attribution is syntactic and approximate: the site's innermost node, the + * operand it acts on, and that operand's declaration. Stage S6 replaces it + * with requests the failing lowering names exactly. */ +import type { ScrDiagnostic } from "../diagnostics/diagnostic.js"; +import type { IrFunction, SrcLoc } from "../ir/ir.js"; +import { dynamicOperationSites, type DynamicFunction } from "../ir/tiers.js"; +import * as ts from "./ts7/adapter.js"; + +/** `--tier`: "auto" (the default) will demote; "static-only" keeps every + * demotion request a refusal (design invariant I2). Identical in S2. */ +export type TierMode = "auto" | "static-only"; + +export const TIER_MODES: readonly TierMode[] = ["auto", "static-only"]; + +export type DemotionUnitKind = "binding" | "field" | "function" | "class" | "module"; + +export interface DemotionUnit { + kind: DemotionUnitKind; + /** Display name: `x`, `Point.x`, `parse`, `Parser.next`, `Point`, or the + * module's file path. */ + name: string; + /** Bindings: the function declaring a local, absent for module scope. */ + owner?: string; + /** The unit's declaration (the site itself when none is known). */ + loc: SrcLoc; +} + +/** Why a unit is demoted (DESIGN.md §6.2): a type with no static + * representation, an operation with no static lowering, reflection on a + * class, a use of a binding whose declaration was refused, a module or + * package that cannot be static, or a construct excluded by policy (no + * demotion: it stays a refusal). */ +export type DemotionSeed = "type" | "operation" | "reflection" | "cascade" | "module" | "excluded"; + +export type DemotionScope = "reached" | "deferred" | "unreached"; + +export interface DemotionRequest { + unit: DemotionUnit; + /** The function instance (or module top level) containing the site. */ + enclosing: DemotionUnit; + seed: DemotionSeed; + scope: DemotionScope; + code: string; + message: string; + loc: SrcLoc; +} + +export interface DemotedUnit extends DemotionUnit { + /** Requests naming this unit directly. */ + requests: number; + /** Requests whose sites this function instance or module contains. */ + sites: number; +} + +export interface TierDynamicFunction extends DynamicFunction { + /** Source statements with a dynamic operation in this function. */ + statements: number; +} + +export interface TierReport { + mode: TierMode; + /** Reached source statements that perform a dynamic operation. */ + statementsDynamic: number; + dynamicFunctions: TierDynamicFunction[]; + requests: DemotionRequest[]; + /** Units the requests would demote (reached and deferred scopes), + * deduplicated, by kind then source order. */ + units: DemotedUnit[]; +} + +/* ── statements of dynamic functions ─────────────────────────────────── */ + +function isClassLike(node: ts.Node): boolean { + return ts.isClassDeclaration(node) || ts.isClassExpression(node); +} + +/** The innermost node spanning exactly or containing [start, end). */ +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()) { + next = child; + return true; + } + return undefined; + }); + if (next === undefined) return node; + node = next; + } +} + +/** The source statement an IR location belongs to: the innermost + * statement containing it, or an arrow function's expression body. Null + * for synthetic locations. */ +function statementAt(program: ts.Program, loc: SrcLoc): ts.Node | null { + if (loc.start === 0 && loc.end === 0) return null; + const sf = program.getSourceFile(loc.file); + if (sf === undefined) return null; + let node: ts.Node | undefined = nodeAt(sf, loc.start, loc.end); + while (node !== undefined && !ts.isSourceFile(node)) { + const parent: ts.Node | undefined = node.parent; + if (parent !== undefined && ts.isArrowFunction(parent) && parent.body === node) return node; + if (ts.isStatement(node) && !ts.isBlock(node)) return node; + node = parent; + } + return null; +} + +/** Dynamic statements are the source statements whose lowering performs a + * dynamic operation (in the function itself or in a generated helper it + * calls). They count once however many functions and operations they + * involve. A function's tier is the unit of I1, but static operations + * inside a dynamic function stay static, so the statement is the honest + * measure of how much code is dynamic. */ +function dynamicFunctionReports( + program: ts.Program, + bodies: readonly IrFunction[], +): { functions: TierDynamicFunction[]; statements: number } { + const byLoc = new Map(); + const all = new Set(); + const functions: TierDynamicFunction[] = []; + for (const fn of bodies) { + const sites = dynamicOperationSites(fn); + if (sites.length === 0) continue; + const own = new Set(); + for (const site of sites) { + const key = `${site.loc.file}\0${site.loc.start}\0${site.loc.end}`; + let statement = byLoc.get(key); + if (statement === undefined) { + statement = statementAt(program, site.loc); + byLoc.set(key, statement); + } + if (statement === null) continue; + own.add(statement); + all.add(statement); + } + functions.push({ + name: fn.name, + loc: fn.loc, + operation: sites[0]!.operation, + operationLoc: sites[0]!.loc, + statements: own.size, + }); + } + return { functions, statements: all.size }; +} + +/* ── demotion requests ───────────────────────────────────────────────── */ + +const TYPE_CODES = new Set([ + "SC2001", + "SC2002", + "SC2003", + "SC2005", + "SC2006", + "SC2007", + "SC2008", + "SC2009", + "SC2011", + "SC1063", + "SC1100", + "SC1101", +]); +const MODULE_CODES = new Set([ + "SC1010", + "SC1012", + "SC1013", + "SC1014", + "SC1015", + "SC1016", + "SC2013", +]); +/** Runtime code generation (`eval`, `new Function`): excluded by policy, + * since the dynamic tier compiles ahead of time (DESIGN.md §12 class X). */ +const EXCLUDED_CODES = new Set(["SC2010"]); +const EXCLUDED_SURFACE = /^'(?:new Function|Function|eval)'/; + +/** Refusals and fences that name a static-tier gap. Type errors, + * environment and target refusals, divergences and internal errors are + * not demotion requests. */ +export function isDemotionCode(code: string): boolean { + return /^SC[12]\d{3}$/.test(code); +} + +function isFunctionLikeOrFile(node: ts.Node): boolean { + return ( + ts.isSourceFile(node) || + ts.isFunctionLikeDeclaration(node) || + ts.isClassStaticBlockDeclaration(node) + ); +} + +function nameText(name: ts.Node | undefined): string | null { + if (name === undefined) return null; + if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name)) return name.text; + if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) return name.text; + return null; +} + +function className(node: ts.Node | undefined): string { + if (node === undefined) return ""; + if ((ts.isClassDeclaration(node) || ts.isClassExpression(node)) && node.name !== undefined) + return node.name.text; + const parent = node.parent; + if (parent !== undefined && ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) + return parent.name.text; + return ""; +} + +/** The display name of a function-like node, as a user would look it up. */ +function functionName(node: ts.Node): string { + if (ts.isSourceFile(node)) return ""; + if (ts.isClassStaticBlockDeclaration(node)) return `${className(node.parent)}.`; + if (ts.isConstructorDeclaration(node)) return `${className(node.parent)}.constructor`; + const own = ts.isFunctionLikeDeclaration(node) ? nameText(node.name) : null; + const parent = node.parent; + if (parent !== undefined && isClassLike(parent) && own !== null) + return `${className(parent)}.${own}`; + if (own !== null) return own; + if (parent !== undefined) { + if (ts.isVariableDeclaration(parent) || ts.isPropertyAssignment(parent)) { + const bound = nameText(parent.name); + if (bound !== null) return bound; + } + if ( + ts.isPropertyDeclaration(parent) && + parent.parent !== undefined && + isClassLike(parent.parent) + ) { + const field = nameText(parent.name); + if (field !== null) return `${className(parent.parent)}.${field}`; + } + 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(); + } + } + const sf = node.getSourceFile(); + const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart(sf)); + return ``; +} + +function locOfNode(node: ts.Node): SrcLoc { + const sf = node.getSourceFile(); + return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; +} + +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(); +} + +function functionUnit(node: ts.Node, instance: string | null): DemotionUnit { + // Top-level code runs in the module's initializer function. + if (ts.isSourceFile(node)) + return { + kind: "function", + name: "", + loc: { file: node.fileName, start: 0, end: 0 }, + }; + const name = functionName(node); + return { + kind: "function", + name: instance === null ? name : `${name} ${instance}`, + loc: locOfNode(node), + }; +} + +/** Generic instances append their instantiation context to diagnostics + * (Lowerer.pushDiag: ` (instantiating 'f' with )`); the instance, not + * the source function, is the unit. Returns the type-argument text. */ +function instanceOf(message: string): string | null { + const at = message.lastIndexOf(" (instantiating "); + if (at < 0 || !message.endsWith(")")) return null; + const context = message.slice(at + 2, -1); + const withAt = context.indexOf(" with "); + return withAt < 0 ? context : context.slice(withAt + 6); +} + +/** Strip wrappers and walk receivers down to the value an operation acts + * on: `a.b.c(…)` → `a`, `(x as T)[k]` → `x`. Records whether the chain + * passes through `.prototype`. */ +function rootOf(expr: ts.Node): { root: ts.Node; viaPrototype: boolean } { + let node = expr; + let viaPrototype = false; + for (;;) { + if ( + ts.isParenthesizedExpression(node) || + ts.isAsExpression(node) || + ts.isNonNullExpression(node) || + ts.isAssertionExpression(node) || + ts.isSatisfiesExpression(node) || + ts.isSpreadElement(node) || + ts.isAwaitExpression(node) + ) { + node = node.expression!; + } else if (ts.isPropertyAccessExpression(node)) { + if (node.name.text === "prototype") viaPrototype = true; + node = node.expression; + } else if (ts.isElementAccessExpression(node) || ts.isCallExpression(node)) { + node = node.expression; + } else if ( + ts.isDeleteExpression(node) || + ts.isTypeOfExpression(node) || + ts.isVoidExpression(node) + ) { + node = node.expression; + } else { + return { root: node, viaPrototype }; + } + } +} + +const REFLECTION_NAMESPACES = new Set(["Object", "Reflect"]); + +/** The expression an operation at `node` acts on. Reflection calls + * (`Object.defineProperty(x, …)`, `Reflect.set(x, …)`) act on their first + * argument; `new C()` and `x instanceof C` on the class value. */ +function subjectOf(node: ts.Node): ts.Node { + let current = node; + // A refused callee or member name: the whole call or access is the site. + const parent = current.parent; + if (parent !== undefined && ts.isPropertyAccessExpression(parent) && parent.name === current) + current = parent; + if (ts.isCallExpression(current)) { + const callee = current.expression; + if ( + ts.isPropertyAccessExpression(callee) && + ts.isIdentifier(callee.expression) && + REFLECTION_NAMESPACES.has(callee.expression.text) && + current.arguments.length > 0 + ) { + return current.arguments[0]!; + } + return callee; + } + if (ts.isNewExpression(current)) return current.expression; + if (ts.isBinaryExpression(current)) { + if (current.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) return current.right; + if (current.operatorToken.kind === ts.SyntaxKind.EqualsToken) return current.left; + } + return current; +} + +/** The declaration owning a type-refused node: the binding, parameter, + * field or function whose declared type has no static representation. */ +function typeOwnerOf(node: ts.Node): ts.Node | null { + let current: ts.Node | undefined = node; + while (current !== undefined && !ts.isSourceFile(current)) { + if ( + ts.isVariableDeclaration(current) || + ts.isParameter(current) || + ts.isBindingElement(current) || + ts.isPropertyDeclaration(current) || + ts.isPropertySignature(current) || + ts.isCatchClause(current) + ) { + return current; + } + if (ts.isFunctionLikeDeclaration(current) || isClassLike(current)) return current; + if (ts.isStatement(current)) return null; + current = current.parent; + } + return null; +} + +function isModuleObjectName(node: ts.Node): boolean { + return ts.isIdentifier(node) && (node.text === "exports" || node.text === "module"); +} + +class UnitResolver { + constructor(private readonly checker: ts.TypeChecker) {} + + /** The unit a declaration node denotes. */ + declarationUnit(decl: ts.Node): DemotionUnit | null { + if (isClassLike(decl)) return { kind: "class", name: className(decl), loc: locOfNode(decl) }; + if (ts.isFunctionLikeDeclaration(decl)) return functionUnit(decl, null); + if (ts.isPropertyDeclaration(decl) || ts.isPropertySignature(decl)) { + const field = nameText(decl.name) ?? ""; + const owner = decl.parent; + const ownerName = owner !== undefined && isClassLike(owner) ? className(owner) : null; + return { + kind: ownerName === null ? "binding" : "field", + name: ownerName === null ? field : `${ownerName}.${field}`, + loc: locOfNode(decl), + }; + } + if (ts.isCatchClause(decl)) { + const binding = decl.variableDeclaration; + if (binding === undefined) return null; + return this.declarationUnit(binding); + } + if (ts.isVariableDeclaration(decl) || ts.isParameter(decl) || ts.isBindingElement(decl)) { + const name = nameText(decl.name) ?? decl.name?.getText() ?? ""; + const scope = enclosingFunction(decl); + const unit: DemotionUnit = { kind: "binding", name, loc: locOfNode(decl) }; + if (ts.isParameter(decl) && ts.isFunctionLikeDeclaration(decl.parent)) + unit.owner = functionName(decl.parent); + else if (!ts.isSourceFile(scope)) unit.owner = functionName(scope); + return unit; + } + return null; + } + + /** The unit an operand expression denotes. */ + valueUnit(expr: ts.Node): { unit: DemotionUnit; reflection: boolean } | null { + const { root, viaPrototype } = rootOf(expr); + const sf = root.getSourceFile(); + if (root.kind === ts.SyntaxKind.ThisKeyword) { + const owner = enclosingFunction(root); + if (ts.isSourceFile(owner)) + return { + unit: { kind: "module", name: sf.fileName, loc: locOfNode(sf) }, + reflection: false, + }; + const cls = owner.parent; + if (cls !== undefined && isClassLike(cls)) { + // `this.f` names a field; `this` itself is the instance. + const access = root.parent; + if ( + access !== undefined && + ts.isPropertyAccessExpression(access) && + access.expression === root + ) { + return { + unit: { + kind: "field", + name: `${className(cls)}.${access.name.text}`, + loc: locOfNode(access), + }, + reflection: false, + }; + } + return { + unit: { kind: "class", name: className(cls), loc: locOfNode(cls) }, + reflection: viaPrototype, + }; + } + return null; + } + if (!ts.isIdentifier(root)) return null; + let symbol = this.checker.getSymbolAtLocation(root); + if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) + symbol = this.checker.getAliasedSymbol(symbol); + const decls = symbol === undefined ? [] : this.checker.declarationsOf(symbol); + const decl = decls.find((d) => !d.getSourceFile().isDeclarationFile); + if (decl === undefined) { + // CommonJS `exports`/`module` as values: the module object. + if (isModuleObjectName(root)) + return { + unit: { kind: "module", name: sf.fileName, loc: locOfNode(sf) }, + reflection: false, + }; + return null; + } + if (ts.isSourceFile(decl)) + return { + unit: { kind: "module", name: decl.fileName, loc: locOfNode(decl) }, + reflection: false, + }; + const unit = this.declarationUnit(decl); + if (unit === null) return null; + // `C.prototype.m = …`, `Object.defineProperty(C.prototype, …)`: the + // class (or the constructor function's prototype object). + if (viaPrototype && (unit.kind === "class" || unit.kind === "function")) + return { unit: { ...unit, kind: "class" }, reflection: true }; + return { unit, reflection: unit.kind === "class" }; + } +} + +function seedOf(code: string, message: string): DemotionSeed { + if (EXCLUDED_CODES.has(code) || EXCLUDED_SURFACE.test(message)) return "excluded"; + if (code === "SC2004") return "cascade"; + if (MODULE_CODES.has(code)) return "module"; + if (TYPE_CODES.has(code)) return "type"; + return "operation"; +} + +/** One request for a committed refusal or fence diagnostic. */ +function requestFor( + program: ts.Program, + resolver: UnitResolver, + diag: ScrDiagnostic, + scope: DemotionScope, +): DemotionRequest | null { + if (!isDemotionCode(diag.code)) return null; + let seed = seedOf(diag.code, diag.message); + const base = { code: diag.code, message: diag.message, loc: diag.loc, scope }; + const sf = program.getSourceFile(diag.loc.file); + if (sf === undefined) { + const unit: DemotionUnit = { kind: "module", name: diag.loc.file, loc: diag.loc }; + return { ...base, unit, enclosing: unit, seed }; + } + const moduleUnit: DemotionUnit = { kind: "module", name: sf.fileName, loc: locOfNode(sf) }; + const node = nodeAt(sf, diag.loc.start, diag.loc.end); + const instance = instanceOf(diag.message); + const owner = ts.isSourceFile(node) ? sf : enclosingFunction(node); + const enclosing = functionUnit(owner, instance); + if (seed === "module" || ts.isSourceFile(node)) + return { ...base, unit: moduleUnit, enclosing, seed }; + let unit: DemotionUnit | null = null; + if (seed === "type") { + const decl = typeOwnerOf(node); + if (decl !== null) unit = resolver.declarationUnit(decl); + } + if (unit === null) { + const value = resolver.valueUnit(subjectOf(node)); + if (value !== null) { + unit = value.unit; + if (value.reflection && seed === "operation") seed = "reflection"; + } + } + return { ...base, unit: unit ?? enclosing, enclosing, seed }; +} + +function unitKey(unit: DemotionUnit): string { + return `${unit.kind}\0${unit.name}\0${unit.owner ?? ""}\0${unit.loc.file}\0${unit.loc.start}`; +} + +const KIND_ORDER: readonly DemotionUnitKind[] = ["module", "class", "field", "function", "binding"]; + +function demotedUnits(requests: readonly DemotionRequest[]): DemotedUnit[] { + const units = new Map(); + const entry = (unit: DemotionUnit): DemotedUnit => { + const key = unitKey(unit); + let found = units.get(key); + if (found === undefined) units.set(key, (found = { ...unit, requests: 0, sites: 0 })); + return found; + }; + for (const request of requests) { + if (request.scope === "unreached" || request.seed === "excluded") continue; + entry(request.unit).requests++; + entry(request.enclosing).sites++; + } + return [...units.values()].sort( + (a, b) => + KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind) || + a.loc.file.localeCompare(b.loc.file) || + a.loc.start - b.loc.start || + a.name.localeCompare(b.name), + ); +} + +export interface TierAnalysisInput { + mode: TierMode; + /** The dynamic-tier IR functions of the reached program. */ + dynamicFunctions: readonly IrFunction[]; + reached: readonly ScrDiagnostic[]; + deferred: readonly ScrDiagnostic[]; + unreached: readonly ScrDiagnostic[]; +} + +/** Coverage's statementsDynamic (see dynamicFunctionReports). */ +export function dynamicStatementCount( + program: ts.Program, + dynamicFunctions: readonly IrFunction[], +): number { + return dynamicFunctionReports(program, dynamicFunctions).statements; +} + +export function analyzeTiers(program: ts.Program, input: TierAnalysisInput): TierReport { + const resolver = new UnitResolver(program.getTypeChecker()); + const requests: DemotionRequest[] = []; + const add = (diags: readonly ScrDiagnostic[], scope: DemotionScope): void => { + for (const diag of diags) { + let request: DemotionRequest | null; + try { + request = requestFor(program, resolver, diag, scope); + } catch { + // Attribution is best effort; a report never fails an analysis. + if (!isDemotionCode(diag.code)) continue; + const unit: DemotionUnit = { kind: "module", name: diag.loc.file, loc: diag.loc }; + request = { + unit, + enclosing: unit, + seed: seedOf(diag.code, diag.message), + scope, + code: diag.code, + message: diag.message, + loc: diag.loc, + }; + } + if (request !== null) requests.push(request); + } + }; + add(input.reached, "reached"); + add(input.deferred, "deferred"); + add(input.unreached, "unreached"); + const dynamic = dynamicFunctionReports(program, input.dynamicFunctions); + return { + mode: input.mode, + statementsDynamic: dynamic.statements, + dynamicFunctions: dynamic.functions, + requests, + units: demotedUnits(requests), + }; +} diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 5ecfc60ae..204344641 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -9,6 +9,7 @@ import { BOOL, bytesOf, canConvertToDyn, + canDynCheckTo, CHILD_T, CRYPTOHASH_T, CRYPTOHMAC_T, @@ -919,6 +920,24 @@ function isDeclaredInAmbientNamespace(d: ts.Declaration, name: string): boolean return false; } +/** A type declared in the shipped WebAssembly namespace (scriptc.d.ts): + * its modules, instances, memories, tables, globals, error classes and the + * Exports/Imports records. */ +export function isWebAssemblyType( + type: ts.Type, + ctx: Pick, +): boolean { + const symbol = type.getAliasSymbol() ?? type.getSymbol(); + if (!symbol) return false; + return ctx.checker + .declarationsOf(symbol) + .some( + (d) => + ctx.isStdlibFile(d.getSourceFile()) && + isDeclaredInAmbientNamespace(d as ts.Declaration, "WebAssembly"), + ); +} + /** True when a type contains a record anywhere (the trigger for the * shape-mismatch diagnostic SC2002 — non-record mismatches are tsc's or * the validator's business). */ @@ -996,6 +1015,10 @@ export type TypeParamTsResolver = (t: ts.Type) => ts.Type | null; * one new field here, not a new parameter at every call site. */ export interface TypeMapperCtx { checker: ts.TypeChecker; + /** JavaScript declarations whose instance or object-literal types only + * the checked-dynamic representation holds (js-dynamic-objects.ts). + * Absent when the program has none: every mapping is unchanged. */ + jsDynamicDeclaration?: (decl: ts.Node) => boolean; /** Set while mapping a generic callable member's erased type * (erasedGenericMemberType): the signature's own type parameters * resolve to `unknown` instead of refusing the mapping. */ @@ -1030,7 +1053,7 @@ export interface TypeMapperCtx { * inside members, self-referential member types), null outside any * mixin context (the type alone cannot name a call site). */ mixinClassInstance?: (decl: ts.ClassLikeDeclaration) => IrType | null; - localClassInstance?: (decl: ts.ClassLikeDeclaration) => IrType | null; + localClassInstance?: (decl: ts.ClassLikeDeclaration, instanceType?: ts.Type) => IrType | null; /** Live class protocols retain identity and receiver dispatch rather than * copying an instance into an unrelated record layout. */ classImplementedProtocol?: (type: ts.Type) => boolean; @@ -1368,6 +1391,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const intrinsic = type.getSymbol(); if (intrinsic?.name === "globalThis" && checker.declarationsOf(intrinsic).length === 0) return DYN; + if (ctx.jsDynamicDeclaration !== undefined && intrinsic !== undefined) { + const objectLiteral = (intrinsic.flags & ts.SymbolFlags.ObjectLiteral) !== 0; + const classInstance = + (intrinsic.flags & ts.SymbolFlags.Class) !== 0 && type.isClassOrInterface(); + if ( + (objectLiteral || classInstance) && + checker.declarationsOf(intrinsic).some((decl) => ctx.jsDynamicDeclaration!(decl)) + ) + return DYN; + } { const moduleId = ctx.moduleNamespaceId?.(type) ?? null; if (moduleId !== null) return { kind: "moduleNs", moduleId }; @@ -1404,6 +1437,14 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return { kind: "object", className: "%Error" }; } if (isOsConstantsType(type, ctx) || isLegacyUrlType(type, ctx)) return DYN; + // The WebAssembly API's values have the checked-dynamic representation, so + // declarations that merely mention them compile; each reached use of the + // API itself is refused at its site (builtins/webassembly.ts). + if (isWebAssemblyType(type, ctx)) return DYN; + // A union over one of them (quickjs-wasi's `BufferSource | WebAssembly.Module` + // 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; // Immutable (@scriptc/types) is a deep-readonly view: the value is // still the T it maps. const immutableSource = immutableSourceType(type, ctx); @@ -1687,16 +1728,38 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Declaration-only package types have no compiled implementation. // Primitive and container types retain their structural mappings; types // owned by the standard library or explicit host mappings are handled - // by their dedicated native rules. - const npmSym = widened.getAliasSymbol() ?? widened.getSymbol(); - const npmDecls = npmSym ? checker.declarationsOf(npmSym) : undefined; + // by their dedicated native rules. Pure type shapes — interfaces, type + // aliases, and object or mapped type literals with no value declaration + // merged in — describe data, not an implementation: a program value of + // such a type is one the program built itself (`import type` from a + // types-only package), so it maps structurally like a local declaration. + // Values the package itself would produce stay refused at their import. + const packageOnly = (sym: ts.Symbol | undefined): readonly ts.Node[] | null => { + const decls = sym ? checker.declarationsOf(sym) : undefined; + return decls && + decls.length > 0 && + decls.every((d) => { + const sf = d.getSourceFile(); + return sf.isDeclarationFile && !ctx.isStdlibFile(sf) && !ctx.isExternalTypeFile(sf); + }) + ? decls + : null; + }; + const pureTypeShape = (decls: readonly ts.Node[] | null): boolean => + decls === null || + decls.every( + (d) => + packageTypeFile(d.getSourceFile(), ctx) && + (ts.isInterfaceDeclaration(d) || + ts.isTypeAliasDeclaration(d) || + ts.isTypeLiteralNode(d) || + ts.isMappedTypeNode(d)), + ); + const aliasDecls = packageOnly(widened.getAliasSymbol()); + const ownDecls = packageOnly(widened.getSymbol()); if ( - npmDecls && - npmDecls.length > 0 && - npmDecls.every((d) => { - const sf = d.getSourceFile(); - return sf.isDeclarationFile && !ctx.isStdlibFile(sf) && !ctx.isExternalTypeFile(sf); - }) + (aliasDecls !== null || (widened.getAliasSymbol() === undefined && ownDecls !== null)) && + !(pureTypeShape(aliasDecls) && pureTypeShape(ownDecls)) ) { return null; } @@ -1913,7 +1976,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } } if (classExprNeedsContext(classDecl)) { - const local = ctx.localClassInstance?.(classDecl); + const local = ctx.localClassInstance?.(classDecl, widened); if (local) contextResolutions++; return local ?? null; } @@ -1952,7 +2015,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } } if (classExprNeedsContext(classDecl)) { - const local = ctx.localClassInstance?.(classDecl); + const ctorSigs = checker.getConstructSignatures(widened); + const local = ctx.localClassInstance?.( + classDecl, + ctorSigs.length === 1 ? checker.getReturnTypeOfSignature(ctorSigs[0]!) : undefined, + ); if (local?.kind === "object") { contextResolutions++; return { kind: "classval", className: local.className }; @@ -3183,7 +3250,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isStdlibInterface("IterableIterator") || // An Iterator annotation holds a single-pass iterator object: a // generator, or a native iterator adapted into one. - isStdlibInterface("Iterator") + isStdlibInterface("Iterator") || + // IteratorObject is what the iterator helpers return: a helper + // generator over its source (containers/iterator-helpers.ts). + isStdlibInterface("IteratorObject") ) { const args = checker.getTypeArguments(widened as ts.TypeReference); const channels = genChannels(args[0], args[1], args[2], ctx); @@ -3878,158 +3948,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if (hybrid) return hybrid; } const callSigs = checker.getCallSignatures(widened); - if (callSigs.length === 1) { - const sig = callSigs[0]!; - if (sig.getTypeParameters().length && ctx.erasingSignature !== sig) return null; - // A SYNTHESIZED rest param (tsc's JS inference for a function body - // reading `arguments` — `(...args: any[]) => any` whose args symbol - // has no declaration): the dotDotDot check below can't see it, and a - // fixed-arity func mapping would LIE about the value's calling - // convention. Detected by the param-count mismatch against the - // signature's own declaration, excluding an erased `this` parameter. - { - const sigDecl = checker.signatureDeclaration(sig); - const declParams = - sigDecl !== undefined && ts.isFunctionLike(sigDecl) ? sigDecl.parameters : undefined; - if ( - declParams !== undefined && - runtimeParameterCount(declParams) !== sig.getParameters().length - ) { - return null; - } - // tsgo never SYNTHESIZES that rest param into the inferred signature - // (5.9.3 did — the count mismatch above was the whole detector - // there), so the declaration's own body answers directly. - if (sigDecl !== undefined && ts.isFunctionLike(sigDecl) && bodyReadsArguments(sigDecl)) { - return null; - } - } - const params: IrType[] = []; - let typedRest = false; - for (const p of sig.getParameters()) { - const decl = checker.valueDeclarationOf(p); - const rest = decl !== undefined && ts.isParameter(decl) && decl.dotDotDotToken !== undefined; - - // 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; - const optional = - decl !== undefined && - ts.isParameter(decl) && - (decl.questionToken !== undefined || decl.initializer !== undefined); - let pt = mapType(checker.getTypeOfSymbol(p), ctx); - if ( - decl && - ts.isParameter(decl) && - ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) - ) - pt = DYN; - // An inferred JavaScript binding pattern consumes an iterable; it - // does not require the caller's array to have exactly this length. - const executor = decl && ts.isParameter(decl) ? decl.parent : null; - const construction = executor?.parent; - const constructorSymbol = - construction && ts.isNewExpression(construction) - ? checker.getSymbolAtLocation(construction.expression) - : undefined; - const nativeExecutor = - executor && - (ts.isArrowFunction(executor) || ts.isFunctionExpression(executor)) && - construction && - ts.isNewExpression(construction) && - construction.arguments?.[0] === executor && - ts.isIdentifier(construction.expression) && - construction.expression.text === "Promise" && - constructorSymbol && - checker - .declarationsOf(constructorSymbol) - .some((site) => ctx.isStdlibFile(site.getSourceFile())); - if ( - !nativeExecutor && - decl && - ts.isParameter(decl) && - !decl.dotDotDotToken && - !ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) && - !decl.type && - !/@(?:param|type)\b/.test( - decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), - ) - ) - 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 - // already spells it that way; arm it here if the world didn't). - if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn") { - const armed = withUndefinedArm(pt, ctx.unions); - if (!armed) return null; - pt = armed; - } - if (!pt) return null; - if (rest) { - if (pt.kind !== "array") return null; - typedRest = true; - } - // `(value: void) => void` (Promise's resolve) is callable with - // no arguments — a void param is dropped, not a mapping failure. - if (pt.kind === "void") continue; - params.push(pt); - } - const retT = checker.getReturnTypeOfSignature(sig); - // `() => never` (a throw-only lambda's inferred type) is assignable to - // `() => void` and its calls never produce a value — map the return - // like declaredReturnType does for declarations. JS null/undefined - // returns can read mutable fields, so callable values use the same - // checked-native return ABI as their implementations. - const sigDecl = checker.signatureDeclaration(sig); - const jsUnitReturn = - sigDecl !== undefined && - isJsSourceFile(sigDecl.getSourceFile()) && - (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; - let ret = - jsUnitReturn || (retT.flags & ts.TypeFlags.Any) !== 0 - ? DYN - : retT.flags & ts.TypeFlags.Never - ? VOID - : mapType(retT, ctx); - if (sigDecl && ts.isGetAccessorDeclaration(sigDecl) && isJsSourceFile(sigDecl.getSourceFile())) - ret = DYN; - if (sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && ret?.kind === "array") ret = DYN; - if ( - sigDecl && - ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && - !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && - ret?.kind === "record" - ) - ret = DYN; - if ( - sigDecl && - ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && - !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && - (ret?.kind === "date" || - (ret?.kind === "union" && - ctx.unions.get(ret.unionId)?.arms.some((arm) => arm.kind === "func"))) - ) - ret = DYN; - if ( - ret && - sigDecl && - isJsSourceFile(sigDecl.getSourceFile()) && - functionCanReturnUndefined(sigDecl) - ) - ret = withUndefinedArm(ret, ctx.unions) ?? ret; - if (!ret) return null; - return typedRest - ? { kind: "func", params, ret, rest: true, restAbi: "typed" } - : funcOf(params, ret); - } + if (callSigs.length === 1) return mapCallSignature(callSigs[0]!, ctx); // Records: object types whose members are all data properties (shorthand // methods in type position count — they're func-typed fields) with // mappable types; no call/construct signatures, no index signatures, not @@ -4303,6 +4222,162 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return null; } +/** One non-overloaded call signature's func type (mapTypeInner's + * single-signature rule; erasedOverloadMemberType maps each overload of a + * member through it). */ +function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null { + const { checker } = ctx; + if (sig.getTypeParameters().length && ctx.erasingSignature !== sig) return null; + // A SYNTHESIZED rest param (tsc's JS inference for a function body + // reading `arguments` — `(...args: any[]) => any` whose args symbol + // has no declaration): the dotDotDot check below can't see it, and a + // fixed-arity func mapping would LIE about the value's calling + // convention. Detected by the param-count mismatch against the + // signature's own declaration, excluding an erased `this` parameter. + { + const sigDecl = checker.signatureDeclaration(sig); + const declParams = + sigDecl !== undefined && ts.isFunctionLike(sigDecl) ? sigDecl.parameters : undefined; + if ( + declParams !== undefined && + runtimeParameterCount(declParams) !== sig.getParameters().length + ) { + return null; + } + // tsgo never SYNTHESIZES that rest param into the inferred signature + // (5.9.3 did — the count mismatch above was the whole detector + // there), so the declaration's own body answers directly. + if (sigDecl !== undefined && ts.isFunctionLike(sigDecl) && bodyReadsArguments(sigDecl)) { + return null; + } + } + const params: IrType[] = []; + let typedRest = false; + for (const p of sig.getParameters()) { + const decl = checker.valueDeclarationOf(p); + const rest = decl !== undefined && ts.isParameter(decl) && decl.dotDotDotToken !== undefined; + + // 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; + const optional = + decl !== undefined && + ts.isParameter(decl) && + (decl.questionToken !== undefined || decl.initializer !== undefined); + let pt = mapType(checker.getTypeOfSymbol(p), ctx); + if ( + decl && + ts.isParameter(decl) && + ts.isSetAccessorDeclaration(decl.parent) && + isJsSourceFile(decl.getSourceFile()) + ) + pt = DYN; + // An inferred JavaScript binding pattern consumes an iterable; it + // does not require the caller's array to have exactly this length. + const executor = decl && ts.isParameter(decl) ? decl.parent : null; + const construction = executor?.parent; + const constructorSymbol = + construction && ts.isNewExpression(construction) + ? checker.getSymbolAtLocation(construction.expression) + : undefined; + const nativeExecutor = + executor && + (ts.isArrowFunction(executor) || ts.isFunctionExpression(executor)) && + construction && + ts.isNewExpression(construction) && + construction.arguments?.[0] === executor && + ts.isIdentifier(construction.expression) && + construction.expression.text === "Promise" && + constructorSymbol && + checker + .declarationsOf(constructorSymbol) + .some((site) => ctx.isStdlibFile(site.getSourceFile())); + if ( + !nativeExecutor && + decl && + ts.isParameter(decl) && + !decl.dotDotDotToken && + !ts.isSetAccessorDeclaration(decl.parent) && + isJsSourceFile(decl.getSourceFile()) && + !decl.type && + !/@(?:param|type)\b/.test( + decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), + ) + ) + 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 + // already spells it that way; arm it here if the world didn't). + if (optional && pt && pt.kind !== "void" && pt.kind !== "dyn") { + const armed = withUndefinedArm(pt, ctx.unions); + if (!armed) return null; + pt = armed; + } + if (!pt) return null; + if (rest) { + if (pt.kind !== "array") return null; + typedRest = true; + } + // `(value: void) => void` (Promise's resolve) is callable with + // no arguments — a void param is dropped, not a mapping failure. + if (pt.kind === "void") continue; + params.push(pt); + } + const retT = checker.getReturnTypeOfSignature(sig); + // `() => never` (a throw-only lambda's inferred type) is assignable to + // `() => void` and its calls never produce a value — map the return + // like declaredReturnType does for declarations. JS null/undefined + // returns can read mutable fields, so callable values use the same + // checked-native return ABI as their implementations. + const sigDecl = checker.signatureDeclaration(sig); + const jsUnitReturn = + sigDecl !== undefined && + isJsSourceFile(sigDecl.getSourceFile()) && + (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; + let ret = + jsUnitReturn || (retT.flags & ts.TypeFlags.Any) !== 0 + ? DYN + : retT.flags & ts.TypeFlags.Never + ? VOID + : mapType(retT, ctx); + if (sigDecl && ts.isGetAccessorDeclaration(sigDecl) && isJsSourceFile(sigDecl.getSourceFile())) + ret = DYN; + if (sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && ret?.kind === "array") ret = DYN; + if ( + sigDecl && + ts.isFunctionLike(sigDecl) && + isJsSourceFile(sigDecl.getSourceFile()) && + !sigDecl.type && + !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + ret?.kind === "record" + ) + ret = DYN; + if ( + sigDecl && + ts.isFunctionLike(sigDecl) && + isJsSourceFile(sigDecl.getSourceFile()) && + !sigDecl.type && + !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + (ret?.kind === "date" || + (ret?.kind === "union" && + ctx.unions.get(ret.unionId)?.arms.some((arm) => arm.kind === "func"))) + ) + ret = DYN; + if ( + ret && + sigDecl && + isJsSourceFile(sigDecl.getSourceFile()) && + functionCanReturnUndefined(sigDecl) + ) + ret = withUndefinedArm(ret, ctx.unions) ?? ret; + if (!ret) return null; + return typedRest + ? { kind: "func", params, ret, rest: true, restAbi: "typed" } + : funcOf(params, ret); +} + /** Retain a literal discriminator before widening erases its values. A * structural match alone is insufficient: `{ kind: "loop", body: ... }` * also fits a smaller `{ kind: "empty" }` record. Choosing that layout @@ -5407,6 +5482,17 @@ function isMappedShape(t: ts.Type): boolean { ); } +/** A declaration file an installed package ships (its own or an @types + * twin), outside the standard library and @types/node: the type surface + * of a package the program does not compile. Its pure type shapes — + * interfaces, aliases, type literals — describe data a program can build + * itself (`import type` from a types-only package such as type-fest). */ +function packageTypeFile(sf: ts.SourceFile, ctx: Pick): boolean { + return ( + sf.isDeclarationFile && /[\\/]node_modules[\\/]/.test(sf.fileName) && !ctx.isStdlibFile(sf) + ); +} + /** The record path's provenance fence. Declared shapes (object literals, * interfaces, type literals) must come from user code or an explicitly * mapped external type surface, never an arbitrary .d.ts — the empty @@ -5445,13 +5531,14 @@ function recordProvenanceOk(t: ts.Type, ctx: TypeMapperCtx): boolean { .every( (decl) => !decl.getSourceFile().isDeclarationFile || - ctx.isExternalTypeFile(decl.getSourceFile()), + ctx.isExternalTypeFile(decl.getSourceFile()) || + packageTypeFile(decl.getSourceFile(), ctx), ), ); } return !decls.some((d) => { const sf = d.getSourceFile(); - return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf); + return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }); } @@ -5537,6 +5624,164 @@ export function erasedGenericMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType return erased?.kind === "func" ? erased : undefined; } +/** An OVERLOADED callable member type (`on(event: "a", h: (x: number) => + * void): () => void; on(event: "b", h: (x: string) => void): () => void` + * in a program interface or type literal): two or more call signatures and + * nothing else (no data properties, construct signatures, or index + * signatures), every one declared in TypeScript program source. Such a + * member keeps a record slot holding its erased runtime function + * (erasedOverloadMemberType); calls convert per their resolved overload + * (lowerRecordFieldCall). */ +export function isOverloadedCallableMemberType(t: ts.Type, checker: ts.TypeChecker): boolean { + const sigs = checker.getCallSignatures(t); + if (sigs.length < 2) return false; + if ( + checker.getConstructSignatures(t).length > 0 || + checker.getPropertiesOfType(t).length > 0 || + checker.getIndexInfosOfType(t).length > 0 + ) + return false; + return sigs.every((sig) => { + const decl = checker.signatureDeclaration(sig); + if (decl === undefined) return false; + if (!ts.isMethodSignatureDeclaration(decl) && !ts.isCallSignatureDeclaration(decl)) + return false; + const file = decl.getSourceFile(); + return !file.isDeclarationFile && !isJsSourceFile(file); + }); +} + +/** Scalar arms an overload join may combine into one union. */ +const OVERLOAD_JOIN_SCALARS = new Set(["f64", "string", "bool", "undefinedT", "nullT"]); + +/** The join of one position's types across an overload set: equal types + * stay; scalar mixes form their union; anything else is `unknown` when + * every joined type crosses the checked-dynamic boundary in both directions + * (the call site converts into the slot, the implementation converts out + * of it). Differing callbacks (`(x: number) => void` beside `(x: string) => + * void`) therefore travel as checked function values, which any + * implementation's own callback type adapts from. Null when no join + * exists. */ +function joinOverloadTypes(types: readonly IrType[], ctx: TypeMapperCtx): IrType | null { + const first = types[0]!; + if (types.every((t) => typeEquals(t, first))) return first; + const getRecord = (id: string) => ctx.shapes.get(id); + const getUnion = (id: string) => ctx.unions.get(id); + const arms = new Map(); + let scalar = true; + for (const t of types) { + const parts = t.kind === "union" ? (ctx.unions.get(t.unionId)?.arms ?? []) : [t]; + if (parts.length === 0) scalar = false; + for (const part of parts) { + if (!OVERLOAD_JOIN_SCALARS.has(part.kind)) scalar = false; + arms.set(typeKey(part), part); + } + } + if (scalar && [...arms.values()].some((arm) => !isUnitType(arm))) { + const sorted = [...arms.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + return { kind: "union", unionId: ctx.unions.intern(sorted) }; + } + if ( + types.every( + (t) => + t.kind === "dyn" || + (t.kind !== "void" && + canConvertToDyn(t, getRecord, getUnion) && + canDynCheckTo(t, getRecord, getUnion)), + ) + ) + return DYN; + return null; +} + +/** An overloaded member's runtime type (isOverloadedCallableMemberType). + * JavaScript stores ONE function under the member; overload signatures only + * constrain call sites. The slot's type joins the overloads per parameter + * position and result (joinOverloadTypes), and a position some overloads + * omit is optional. Each overload's own type parameters erase to `unknown` + * first. Calls pick their overload statically, convert arguments into the + * slot's parameter types and the result back to the overload's; an + * implementation converts into the slot like any function value. Undefined + * when the set does not join (rest parameters, a void result beside a + * value result, a component outside the checked-dynamic boundary): the + * member keeps its fence. */ +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 outer = ctx.resolveTypeParam; + const mapped = mapCallSignature( + sig, + own.size === 0 + ? ctx + : { + ...ctx, + erasingSignature: sig, + resolveTypeParam: (param) => (own.has(param) ? DYN : (outer?.(param) ?? null)), + }, + ); + if (mapped?.kind !== "func" || mapped.rest === true || mapped.argumentsAll === true) + return undefined; + funcs.push(mapped); + } + const arity = Math.max(...funcs.map((f) => f.params.length)); + const params: IrType[] = []; + for (let i = 0; i < arity; i++) { + const present = funcs.filter((f) => i < f.params.length).map((f) => f.params[i]!); + let param = joinOverloadTypes(present, ctx); + if (param !== null && present.length < funcs.length && param.kind !== "dyn") + param = withUndefinedArm(param, ctx.unions); + if (param === null) return undefined; + params.push(param); + } + const voids = funcs.filter((f) => f.ret.kind === "void").length; + if (voids !== 0 && voids !== funcs.length) return undefined; + const ret = + voids === funcs.length + ? VOID + : joinOverloadTypes( + funcs.map((f) => f.ret), + ctx, + ); + if (ret === null) return undefined; + return funcOf(params, ret); +} + +/** Why an overloaded member's set has no erased type + * (erasedOverloadMemberType): the first overload, and within it the first + * parameter or result, that does not map; otherwise the join itself. A + * message tail after "its member 'name'". */ +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 outer = ctx.resolveTypeParam; + const sigCtx: TypeMapperCtx = + own.size === 0 + ? ctx + : { + ...ctx, + erasingSignature: sig, + resolveTypeParam: (param) => (own.has(param) ? DYN : (outer?.(param) ?? null)), + }; + const mapped = mapCallSignature(sig, sigCtx); + if (mapped?.kind === "func" && mapped.rest !== true && mapped.argumentsAll !== true) continue; + const decl = checker.signatureDeclaration(sig); + const spelled = decl ? decl.getText().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`; + } + const retTs = checker.getReturnTypeOfSignature(sig); + return `is an overload set, and its ${which} returns '${checker.typeToString(retTs)}', which does not compile`; + } + return "is an overload set whose signatures do not join into one stored function (a void result beside a value result, or positions whose differing types do not cross the checked-dynamic boundary)"; +} + /** The computed pieces of a record shape mapRecordTypeInner hands back for * the OUTER frame to intern — or, when a back-reference minted a recursive * placeholder for the type mid-construction, to finalize INTO that @@ -5908,7 +6153,7 @@ function mapRecordTypeInner( computed && checker.declarationsOf(p).some((d) => { const sf = d.getSourceFile(); - return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf); + return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { return null; @@ -5918,9 +6163,14 @@ function mapRecordTypeInner( // can hold them — see isGenericCallableMemberType): the shape keeps // its data fields, and calls of the member monomorphize per call // site against the defining object literal's declaration. + // OVERLOADED members hold their erased runtime function (see + // erasedOverloadMemberType); a set that does not join fails the + // shape as before. let pt = isGenericCallableMemberType(fieldTs, checker) ? erasedGenericMemberType(fieldTs, ctx) - : mapType(fieldTs, ctx); + : isOverloadedCallableMemberType(fieldTs, checker) + ? (erasedOverloadMemberType(fieldTs, ctx) ?? null) + : mapType(fieldTs, ctx); if (pt === undefined) continue; // tsgo PANICS computing `readonly []` through the symbol-type query // (the TupleType conversion — the facade's panic fence answers @@ -6235,12 +6485,16 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx computed && checker.declarationsOf(p).some((d) => { const sf = d.getSourceFile(); - return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf); + return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { return null; } 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)}`; + } let pt = isGenericCallableMemberType(fieldTs, checker) ? erasedGenericMemberType(fieldTs, ctx) : mapType(fieldTs, ctx); diff --git a/packages/compiler/src/frontend/type-only-imports.ts b/packages/compiler/src/frontend/type-only-imports.ts new file mode 100644 index 000000000..003d4ccf7 --- /dev/null +++ b/packages/compiler/src/frontend/type-only-imports.ts @@ -0,0 +1,143 @@ +/** Type-only module edges under Node's type stripping. + * + * Node runs TypeScript by erasing types in place, with the semantics of + * `verbatimModuleSyntax`: `import type …` and `export type … from` vanish, + * but `import { type A } from "./m"` and `export { type A } from "./m"` + * keep an empty clause (`import {} from "./m"`) and still load and + * evaluate `./m`. Only the erased forms are absent from Node's module + * graph. */ +import * as ts from "./ts7/adapter.js"; +import { moduleSourceCandidates } from "./module-source-candidates.js"; + +/** `import type …` (default, named, or namespace): erased by Node, so + * module order, cycle detection, init calls, package selection, and the + * startup-refusal probe must not count it (zod v4's core modules are + * "cyclic" only through `import type * as ns` namespaces, and a + * type-only import of a module Node cannot resolve is not a startup + * crash). Side-effect imports and inline `type` specifiers stay real + * edges. */ +export function erasedTypeOnlyImport(stmt: ts.ImportDeclaration): boolean { + return stmt.importClause?.phaseModifier === ts.SyntaxKind.TypeKeyword; +} + +/** `import { type A, type B } from "./m"`: a real module edge that binds + * nothing at runtime, exactly like a side-effect import. */ +export function typeOnlyNamedImport(stmt: ts.ImportDeclaration): boolean { + const clause = stmt.importClause; + return ( + clause !== undefined && + clause.phaseModifier !== ts.SyntaxKind.TypeKeyword && + clause.name === undefined && + clause.namedBindings !== undefined && + ts.isNamedImports(clause.namedBindings) && + clause.namedBindings.elements.length > 0 && + clause.namedBindings.elements.every((e) => e.isTypeOnly) + ); +} + +/** One module reference of a program file: its specifier and whether + * Node erases it. */ +interface ModuleReference { + spec: string; + erased: boolean; + kind?: "import" | "require"; +} + +function declarationReferencesOf(sf: ts.SourceFile): ModuleReference[] { + const refs: ModuleReference[] = []; + for (const stmt of sf.statements) { + if (ts.isImportDeclaration(stmt)) { + if (ts.isStringLiteral(stmt.moduleSpecifier)) + refs.push({ spec: stmt.moduleSpecifier.text, erased: erasedTypeOnlyImport(stmt) }); + } else if (ts.isExportDeclaration(stmt)) { + if (stmt.moduleSpecifier !== undefined && ts.isStringLiteral(stmt.moduleSpecifier)) + refs.push({ spec: stmt.moduleSpecifier.text, erased: stmt.isTypeOnly }); + } else if ( + ts.isImportEqualsDeclaration(stmt) && + ts.isExternalModuleReference(stmt.moduleReference) && + ts.isStringLiteralLike(stmt.moduleReference.expression) + ) { + refs.push({ + spec: stmt.moduleReference.expression.text, + erased: stmt.isTypeOnly, + kind: "require", + }); + } + } + return refs; +} + +/** Every call with a leading string literal counts as a loading edge — + * require(), import(), createRequire'd require functions, and loader + * helpers alike. Over-counting only keeps a file in the graph. */ +function callReferencesOf(program: ts.Program, sf: ts.SourceFile): ModuleReference[] { + const refs: ModuleReference[] = []; + for (const node of moduleSourceCandidates(program, sf).calls) { + const arg = node.arguments[0]; + if (arg === undefined || !ts.isStringLiteralLike(arg)) continue; + refs.push({ + spec: arg.text, + erased: false, + kind: node.expression.kind === ts.SyntaxKind.ImportKeyword ? "import" : "require", + }); + } + return refs; +} + +/** The program files Node never loads because the only references that + * reach them are erased type-only forms (directly or through other such + * files). A file referenced by nothing at all (a program root, or a file + * reached through a reference this walk does not model, such as an + * `import("./m")` type query) keeps its place, as do `extraRoots` (fork + * and worker targets). Empty when no erased reference resolves to a + * program file — the common case pays one statement scan per file. */ +export function typeOnlyReachedFiles( + program: ts.Program, + entry: ts.SourceFile, + files: readonly ts.SourceFile[], + extraRoots: () => readonly string[], + resolveEdge: ( + from: ts.SourceFile, + spec: string, + resolutionKind?: "import" | "require", + ) => ts.SourceFile | null, +): Set { + const available = new Set(files); + const declarationRefs = new Map(files.map((sf) => [sf, declarationReferencesOf(sf)] as const)); + const target = (sf: ts.SourceFile, ref: ModuleReference): ts.SourceFile | null => { + const dep = resolveEdge(sf, ref.spec, ref.kind); + return dep === null || dep === sf || !available.has(dep) ? null : dep; + }; + if ( + !files.some((sf) => + declarationRefs.get(sf)!.some((ref) => ref.erased && target(sf, ref) !== null), + ) + ) + return new Set(); + const valueEdges = new Map(); + const referenced = new Set(); + for (const sf of files) { + const deps: ts.SourceFile[] = []; + valueEdges.set(sf, deps); + for (const ref of [...declarationRefs.get(sf)!, ...callReferencesOf(program, sf)]) { + const dep = target(sf, ref); + if (dep === null) continue; + referenced.add(dep); + if (!ref.erased) deps.push(dep); + } + } + const roots = files.filter((sf) => sf === entry || !referenced.has(sf)); + for (const path of extraRoots()) { + const sf = program.getSourceFile(path); + if (sf !== undefined && available.has(sf)) roots.push(sf); + } + const reached = new Set(); + const queue = [...roots]; + while (queue.length > 0) { + const sf = queue.pop()!; + if (reached.has(sf)) continue; + reached.add(sf); + for (const dep of valueEdges.get(sf) ?? []) if (!reached.has(dep)) queue.push(dep); + } + return new Set(files.filter((sf) => !reached.has(sf))); +} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index c3009665a..56b787e18 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -172,11 +172,22 @@ export { } from "./diagnostics/render.js"; export { renderCoverage, + renderExplainDynamic, coverageEnvelope, coveragePasses, type CoverageInput, type CoverageFailOn, } from "./coverage/report.js"; +export { + TIER_MODES, + type DemotedUnit, + type DemotionRequest, + type DemotionSeed, + type DemotionUnit, + type DemotionUnitKind, + type TierMode, + type TierReport, +} from "./frontend/tier-analysis.js"; export { buildEnvelope, DIAGNOSTICS_SCHEMA_VERSION, @@ -208,6 +219,13 @@ export { } from "./compat/fetch-profile.js"; export { LIB_FN_SIGS } from "./ir/builtin-signatures.js"; export { validateModule } from "./ir/validate.js"; +export { + assignLegacyTiers, + dynamicOperationOf, + functionTier, + type DynamicFunction, + type IrTier, +} from "./ir/tiers.js"; export { deserializeModule, deserializeModuleBytes, diff --git a/packages/compiler/src/ir/array-length-stability.test.ts b/packages/compiler/src/ir/array-length-stability.test.ts new file mode 100644 index 000000000..44e461ca2 --- /dev/null +++ b/packages/compiler/src/ir/array-length-stability.test.ts @@ -0,0 +1,141 @@ +import { expect, test } from "vitest"; +import { findUnaliasedLocalArrays, preservesArrayLength } from "./array-length-stability.js"; +import { + BOOL, + F64, + arrayOf, + type IrExpr, + type IrFunction, + type IrLocal, + type IrStmt, +} from "./ir.js"; + +const loc = { file: "array-length-stability.ts", start: 0, end: 0 }; +const ARR = arrayOf(F64); +const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); +const ref = (localId: string): IrExpr => ({ kind: "varRef", localId, type: F64, loc }); +const arr = (localId: string): IrExpr => ({ kind: "varRef", localId, type: ARR, loc }); +const method = ( + receiver: string, + name: Extract["method"], + args: IrExpr[] = [], +): IrExpr => ({ + kind: "arrIntrinsic", + method: name, + receiver: arr(receiver), + args, + type: F64, + loc, +}); +const length = (receiver: string): IrExpr => method(receiver, "length"); +const read = (receiver: string, index: IrExpr): IrExpr => method(receiver, "getNumber", [index]); +const plus = (left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "bin", + op: "+", + left, + right, + type: F64, + loc, +}); +const stmt = (expr: IrExpr): IrStmt => ({ kind: "exprStmt", expr, loc }); +const set = (receiver: string, index: IrExpr, value: IrExpr): IrStmt => ({ + kind: "arraySet", + arr: arr(receiver), + index, + value, + loc, +}); +const cond: IrExpr = { kind: "bin", op: "<", left: ref("i"), right: length("a"), type: BOOL, loc }; +const sum: IrStmt = { + kind: "assign", + localId: "s", + value: plus(ref("s"), read("a", ref("i"))), + loc, +}; +const locals = new Map([ + ["i", { id: "i", name: "i", type: F64, mutable: true }], + ["s", { id: "s", name: "s", type: F64, mutable: true }], + ["a", { id: "a", name: "a", type: ARR, mutable: false }], + ["b", { id: "b", name: "b", type: ARR, mutable: false }], + ["out", { id: "out", name: "out", type: ARR, mutable: false }], +]); +const preserves = ( + body: IrStmt[], + inRange: string | null = "i", + unaliased: ReadonlySet = new Set(), + scope = locals, +): boolean => preservesArrayLength([cond, ...body], { receiver: "a", inRange }, scope, unaliased); + +test("reads, local arithmetic and element writes at the in-range counter keep the length", () => { + expect(preserves([sum])).toBe(true); + expect(preserves([set("a", ref("i"), num(1)), set("b", ref("i"), read("a", ref("i")))])).toBe( + true, + ); + expect(preserves([stmt(method("a", "indexOf", [num(3)]))])).toBe(true); +}); + +test("writes past the counter, without an in-range counter, or to the counter can grow it", () => { + expect(preserves([set("a", plus(ref("i"), num(1)), num(1))])).toBe(false); + expect(preserves([set("b", ref("s"), num(1))])).toBe(false); + expect(preserves([set("a", ref("i"), num(1))], null)).toBe(false); + expect(preserves([{ kind: "assign", localId: "i", value: num(0), loc }, sum])).toBe(false); +}); + +test("resizing methods, length stores, calls and escapes refuse the proof", () => { + for (const name of ["push", "pop", "shift", "unshift", "splice", "fill"] as const) + expect(preserves([stmt(method("a", name, [num(1)]))])).toBe(false); + // An unrelated array may alias the receiver. + expect(preserves([stmt(method("b", "push", [num(1)]))])).toBe(false); + expect(preserves([{ kind: "arraySetLength", arr: arr("a"), length: num(0), loc }])).toBe(false); + expect(preserves([{ kind: "arrayDelete", arr: arr("a"), index: ref("i"), loc }])).toBe(false); + expect(preserves([stmt({ kind: "call", callee: "f", args: [], type: F64, loc })])).toBe(false); + expect(preserves([stmt({ kind: "call", callee: "f", args: [arr("a")], type: F64, loc })])).toBe( + false, + ); + expect(preserves([{ kind: "varDecl", localId: "b", init: arr("a"), loc }])).toBe(false); + expect(preserves([{ kind: "assign", localId: "a", value: arr("b"), loc }])).toBe(false); +}); + +test("an unaliased local array other than the receiver may grow", () => { + const push = stmt(method("out", "push", [read("a", ref("i"))])); + expect(preserves([push], "i", new Set(["out"]))).toBe(true); + expect(preserves([set("out", ref("s"), num(1))], "i", new Set(["out"]))).toBe(true); + expect(preserves([push])).toBe(false); + // The receiver itself is never resized, even when no alias exists. + expect(preserves([stmt(method("a", "push", [num(1)]))], "i", new Set(["a"]))).toBe(false); +}); + +test("captured receivers have no local proof", () => { + const boxed = new Map(locals); + boxed.set("a", { ...locals.get("a")!, boxed: true }); + expect(preserves([sum], "i", new Set(), boxed)).toBe(false); +}); + +function fn(body: IrStmt[], params: string[] = []): IrFunction { + return { + loc, + name: "f", + params: params.map((localId) => ({ localId, type: ARR })), + returnType: ARR, + locals: [...locals.values()], + body, + } as IrFunction; +} +const fresh: IrExpr = { kind: "arrayLit", elems: [], type: ARR, loc }; + +test("fresh local arrays read only as receivers have no alias", () => { + const body: IrStmt[] = [ + { kind: "varDecl", localId: "out", init: fresh, loc }, + stmt(method("out", "push", [num(1)])), + { kind: "return", value: arr("out"), loc }, + ]; + expect([...findUnaliasedLocalArrays(fn(body))]).toEqual(["out"]); + // Parameters, copies into another binding, and arguments escape. + expect(findUnaliasedLocalArrays(fn(body, ["out"])).size).toBe(0); + const copied = [...body, { kind: "varDecl", localId: "b", init: arr("out"), loc } as IrStmt]; + expect(findUnaliasedLocalArrays(fn(copied)).has("out")).toBe(false); + const passed = [...body, stmt({ kind: "call", callee: "g", args: [arr("out")], type: F64, loc })]; + expect(findUnaliasedLocalArrays(fn(passed)).has("out")).toBe(false); + const rebound = [...body, { kind: "assign", localId: "out", value: arr("a"), loc } as IrStmt]; + expect(findUnaliasedLocalArrays(fn(rebound)).has("out")).toBe(false); +}); diff --git a/packages/compiler/src/ir/array-length-stability.ts b/packages/compiler/src/ir/array-length-stability.ts new file mode 100644 index 000000000..2ac196d30 --- /dev/null +++ b/packages/compiler/src/ir/array-length-stability.ts @@ -0,0 +1,259 @@ +import type { IrExpr, IrFunction, IrLocal, IrStmt } from "./ir.js"; +import { isBorrowSafeMath } from "./analysis.js"; +import { byteNumberAccess } from "./byte-numbers.js"; +import { everyExprChild, everyStmtChild } from "./traverse.js"; + +/** Array methods that read the receiver without changing its length or its + * slot states and run no user code. */ +const READING_ARRAY_METHODS = new Set([ + "length", + "getNumber", + "indexEq", + "indexOf", + "includes", + "nextPresent", +]); + +/** String methods that read an immutable string and run no user code. */ +const READING_STRING_METHODS = new Set(["length", "charCodeAt", "codePointAt"]); + +/** Expressions that compute from their operands and run no user code. Their + * children are checked separately. */ +const PLAIN_EXPRESSIONS = new Set([ + "numLit", + "strLit", + "boolLit", + "unitLit", + "varRef", + "bin", + "unary", + "incDec", + "toBool", + "logical", + "ternary", + "strEq", + "strCmp", + "fieldGet", + "recordGet", + "unionNarrow", + "unionIsTag", + "unionDisc", + "unionWrap", + "upcast", + "downcast", + "instanceOf", + "nullish", + "seqExpr", +]); + +/** Statements whose own effect is a local write or control flow inside the + * loop. Their children are checked separately. */ +const PLAIN_STATEMENTS = new Set([ + "varDecl", + "assign", + "exprStmt", + "if", + "block", + "break", + "continue", + "for", + "while", + "doWhile", +]); + +/** Array methods whose result is never the receiver itself (unlike + * `reverse`, `fill` or `copyWithin`, which return it). */ +const CONTAINED_RECEIVER_METHODS = new Set([ + ...READING_ARRAY_METHODS, + "push", + "pop", + "shift", + "unshift", + "splice", + "join", + "slice", +]); + +/** Local arrays that no other binding can hold: every write of the local + * stores a fresh array literal and every read of it is the receiver of an + * element access or array method (or the value of a `return`, which ends the + * activation). No alias of such an array exists, so mutating it cannot change + * the length of an array held by any other binding. */ +export function findUnaliasedLocalArrays(fn: IrFunction): ReadonlySet { + const candidates = new Set(); + const captured = new Set( + [...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId), + ); + for (const local of fn.locals) + if (local.type.kind === "array" && !local.boxed && !local.tdz && !captured.has(local.id)) + candidates.add(local.id); + for (const param of fn.params) candidates.delete(param.localId); + const written = new Set(); + const fresh = (value: IrExpr | null): boolean => + value !== null && (value.kind === "arrayLit" || value.kind === "arrayNewLen"); + const receiver = (e: IrExpr): void => { + if (e.kind !== "varRef") expr(e); + }; + const expr = (e: IrExpr): boolean => { + switch (e.kind) { + case "varRef": + candidates.delete(e.localId); + return true; + case "assignExpr": + candidates.delete(e.localId); + break; + case "arrIntrinsic": + if (!CONTAINED_RECEIVER_METHODS.has(e.method)) break; + receiver(e.receiver); + e.args.forEach(expr); + return true; + case "arrayGet": + case "arrayHas": + case "arrayState": + receiver(e.arr); + expr(e.index); + return true; + } + return everyExprChild(e, expr, stmt); + }; + const stmt = (s: IrStmt): boolean => { + switch (s.kind) { + case "varDecl": + case "assign": { + const value = s.kind === "varDecl" ? s.init : s.value; + if (fresh(value)) written.add(s.localId); + else if (value) candidates.delete(s.localId); + if (value) expr(value); + return true; + } + case "arraySet": + receiver(s.arr); + expr(s.index); + expr(s.value); + return true; + case "return": + if (s.value) receiver(s.value); + return true; + case "forOf": + case "tryCatch": + if (s.kind === "forOf") candidates.delete(s.localId); + if (s.kind === "tryCatch" && s.catchLocalId) candidates.delete(s.catchLocalId); + break; + } + return everyStmtChild(s, expr, stmt); + }; + fn.body.every(stmt); + // A binding with no visible write takes its value some other way. + for (const id of candidates) if (!written.has(id)) candidates.delete(id); + return candidates; +} + +export interface ArrayLengthLoop { + /** The local holding the array whose length must not change. */ + receiver: string; + /** A binding that holds a value below the array's length wherever the + * loop's condition, body or update reads it (the induction variable of + * `for (let i = 0; i < a.length; i++)` when the body never writes it, as + * long as the length itself has not changed). An element write at this + * index replaces an existing element or sets a non-index property. */ + inRange: string | null; +} + +/** Whether running `nodes` (a loop's condition, body and update) can never + * change the length of the array held by local `loop.receiver`. The proof is + * syntactic and conservative: every expression and statement must be one + * that runs no user code (no calls, callbacks, closures, iterators, + * suspension or exception regions); the binding is never written and only + * ever read as the receiver of an element access or a reading array method, + * so the array cannot escape to code that resizes it; and nothing in the + * nodes resizes any array that may be the same one. Element writes are + * accepted at the in-range binding (no array grows by them: an alias of the + * receiver has the same length) and anywhere in an unaliased local array. + * Array methods that resize an array are accepted only on unaliased local + * arrays other than the receiver. */ +export function preservesArrayLength( + nodes: readonly (IrExpr | IrStmt)[], + loop: ArrayLengthLoop, + locals: ReadonlyMap, + unaliased: ReadonlySet, +): boolean { + const local = locals.get(loop.receiver); + if (!local || local.type.kind !== "array" || local.boxed || local.tdz) return false; + const separate = (e: IrExpr): boolean => + e.kind === "varRef" && e.localId !== loop.receiver && unaliased.has(e.localId); + // A receiver position: the receiver binding itself is read there without + // escaping. + const receiver = (e: IrExpr): boolean => + (e.kind === "varRef" && e.localId === loop.receiver) || expr(e); + const written = (id: string): boolean => id === loop.receiver || id === loop.inRange; + const expr = (e: IrExpr): boolean => { + switch (e.kind) { + case "varRef": + return e.localId !== loop.receiver; + case "assignExpr": + case "incDec": + return !written(e.localId) && everyExprChild(e, expr, stmt); + case "arrayGet": + case "arrayHas": + case "arrayState": + return receiver(e.arr) && expr(e.index); + case "arrIntrinsic": + if (READING_ARRAY_METHODS.has(e.method)) return receiver(e.receiver) && e.args.every(expr); + return separate(e.receiver) && e.args.every(expr); + case "strIntrinsic": + return ( + READING_STRING_METHODS.has(e.method) && + e.receiver.type.kind === "string" && + expr(e.receiver) && + e.args.every(expr) + ); + case "bytesIntrinsic": { + const numeric = byteNumberAccess(e); + return ( + (numeric ? !numeric.write : e.method === "get" || e.method === "length") && + expr(e.receiver) && + e.args.every(expr) + ); + } + case "libCall": + return ( + isBorrowSafeMath(e.fn) && + e.args.every((arg) => arg.type.kind === "f64") && + e.args.every(expr) + ); + case "strConcat": + return ( + e.left.type.kind === "string" && + e.right.type.kind === "string" && + expr(e.left) && + expr(e.right) + ); + default: + return PLAIN_EXPRESSIONS.has(e.kind) && everyExprChild(e, expr, stmt); + } + }; + const stmt = (s: IrStmt): boolean => { + switch (s.kind) { + case "varDecl": + case "assign": + if (written(s.localId)) return false; + break; + case "arraySet": + return ( + (separate(s.arr) || + (s.index.kind === "varRef" && + loop.inRange !== null && + s.index.localId === loop.inRange)) && + receiver(s.arr) && + expr(s.index) && + expr(s.value) + ); + case "return": + return s.value === null || expr(s.value); + default: + if (!PLAIN_STATEMENTS.has(s.kind)) return false; + } + return everyStmtChild(s, expr, stmt); + }; + return nodes.every((node) => ("type" in node ? expr(node as IrExpr) : stmt(node as IrStmt))); +} diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 1bb9cbd14..a62046565 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -137,6 +137,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "fetch.streamFrom", "num.toFixed", "num.toStringRadix", + "num.toStringRadixF64", "insp.jsonDyn", // diagnostics_channel: publish runs subscribers synchronously (a throw // propagates — the documented divergence from triggerUncaughtException); @@ -489,6 +490,13 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "str.encodeUri", "qs.escape", "qs.stringify", + // node:punycode's RangeErrors (encode/toASCII only past 32-bit deltas) + // and String.fromCodePoint's for ucs2.encode. + "punycode.decode", + "punycode.encode", + "punycode.toASCII", + "punycode.toUnicode", + "punycode.ucs2Encode", // The base64 globals: atob/btoa throw the catchable DOMException // InvalidCharacterError on malformed input; the zero-argument form // always throws Node's TypeError [ERR_MISSING_ARGS]. @@ -686,6 +694,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "crypto.scryptCb", "buffer.concat", "buffer.concatLen", + "buffer.concatDyn", + "buffer.concatLenDyn", "buffer.fromDyn", "buffer.fromStrChecked", "buffer.alloc", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 780918333..e61bb2594 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -563,6 +563,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** os.constants: the per-thread null-prototype constants object (dlopen, * errno, signals, priority, UV_UDP_REUSEADDR). Never throws. */ "os.constants": { argTypes: [], result: DYN }, + /** The deprecated node:constants object (scr_os.c): the os groups and + * fs.constants merged with the borrowed baked crypto.constants table, + * frozen, one per thread. */ + "constants.object": { argTypes: [DYN], result: DYN }, /** umask(2): arg < 0 reads without setting (umask has no read-only form * — set 0, restore); otherwise sets and answers the PREVIOUS mask. * Never throws. */ @@ -751,6 +755,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "num.toFixed0": { argTypes: [F64], result: STRING }, "num.toFixed": { argTypes: [F64, F64], result: STRING }, "num.toStringRadix": { argTypes: [F64, DYN], result: STRING }, + /** toString(radix) with a number radix (scr_lib.c): truncates it as + * ToIntegerOrInfinity does, THROWS RangeError outside 2..36. */ + "num.toStringRadixF64": { argTypes: [F64, F64], result: STRING }, /** Object.is over two numbers — the spec's SameValue on doubles: NaN * equals NaN, +0 differs from -0, everything else is `===`. Plain bool * result; never throws. (Union-armed operands take unionEq's sameValue @@ -861,6 +868,19 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "url.legacyResolve": { argTypes: [DYN, DYN, BOOL], result: STRING }, "url.domainToASCII": { argTypes: [DYN, BOOL], result: STRING }, "url.domainToUnicode": { argTypes: [DYN, BOOL], result: STRING }, + /** node:punycode (scr_url.c): punycode.js 2.1.0's string conversions; + * decode/toUnicode (and, past 32-bit deltas, encode/toASCII) throw the + * module's RangeErrors, ucs2Encode String.fromCodePoint's. warn marks the + * module's first load (scr_async_dyn.c) and reports DEP0040 then unless + * the second argument is false: at once for a static ES import, on the + * next tick for a require() call. */ + "punycode.decode": { argTypes: [STRING], result: STRING }, + "punycode.encode": { argTypes: [STRING], result: STRING }, + "punycode.toASCII": { argTypes: [STRING], result: STRING }, + "punycode.toUnicode": { argTypes: [STRING], result: STRING }, + "punycode.ucs2Decode": { argTypes: [STRING], result: arrayOf(F64) }, + "punycode.ucs2Encode": { argTypes: [arrayOf(F64)], result: STRING }, + "punycode.warn": { argTypes: [BOOL, BOOL], result: VOID }, "url.fileURLToPathBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 }, "url.pathToFileURLChecked": { argTypes: [DYN, DYN], result: URL_T }, "url.fileURLToPathStr": { argTypes: [STRING], result: STRING }, @@ -1438,7 +1458,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ }, // The suite body takes no parameters or its SuiteContext (flag 2). "test.suite": { argTypes: [STRING, F64, STRING, null, STRING, F64, F64], result: VOID }, - "test.hook": { argTypes: [F64, null, F64], result: VOID }, + // The trailing string is the hook call's "file:line:col". + "test.hook": { argTypes: [F64, null, F64, STRING], result: VOID }, "test.sub": { argTypes: [TESTCTX_T, STRING, F64, STRING, null, F64, STRING, F64, F64, F64], result: { kind: "promise", inner: VOID }, @@ -2623,6 +2644,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * opaque reference inputs retain explicit runtime refusals. */ "buffer.fromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 }, "buffer.concat": { argTypes: [arrayOf(BYTES_U8)], result: BYTES_U8 }, + "buffer.concatDyn": { argTypes: [DYN], result: BYTES_U8 }, /** The pooled Buffer factories (Node's allocate/fromStringFast/ * fromArrayLike): allocUnsafe, Buffer.from of strings, Uint8Arrays, * number[] and checked values, and concat return views into the @@ -2649,6 +2671,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * zero-padded to the total. THROWS Node's 'length' RangeError on a * negative/non-integer total (may-throw seed). */ "buffer.concatLen": { argTypes: [arrayOf(BYTES_U8), F64], result: BYTES_U8 }, + "buffer.concatLenDyn": { argTypes: [DYN, F64], result: BYTES_U8 }, // The checked-dynamic compare/equals validators (Node's argument // ladders over dyn-boxed invalid-input probes). /** The checked-dynamic compare/equals validators (scr_bytes_io.c) — diff --git a/packages/compiler/src/ir/integer-loop-facts.ts b/packages/compiler/src/ir/integer-loop-facts.ts index 4c1a1ef1f..84fabf956 100644 --- a/packages/compiler/src/ir/integer-loop-facts.ts +++ b/packages/compiler/src/ir/integer-loop-facts.ts @@ -118,6 +118,9 @@ export function boundedIntegerLoopFacts( stable(e.receiver.localId) ) return { min: 0, max: Number.MAX_SAFE_INTEGER }; + // Every evaluation of an array length lies in its range, so the + // condition bounds each pass even when the body resizes the array. + if (isArrayLength(e)) return ARRAY_LENGTH_RANGE; return null; } let step: IntegerRange; @@ -134,12 +137,15 @@ export function boundedIntegerLoopFacts( if (step.min <= 0 || step.max > 2147483647) return result; } const limit = bound(loop.cond.right); - if (!limit || limit.max < start.min) return result; - const distance = limit.max - start.min; + // The counter is an integer, so a strict bound admits passes up to one + // below the limit. + const last = limit && (loop.cond.op === "<" ? limit.max - 1 : limit.max); + if (!limit || last === null || last < start.min) return result; + const distance = last - start.min; const iterations = Math.floor(distance / step.min) + 1; if (!Number.isSafeInteger(distance) || !Number.isSafeInteger(iterations)) return result; // Header facts also cover the failing final condition and zero iterations. - const maximum = Math.max(start.max, limit.max + step.max); + const maximum = Math.max(start.max, last + step.max); if (Number.isSafeInteger(maximum)) result.set(counter, { min: start.min, max: maximum }); for (const [id, hi] of upper) { const range = initial.get(id); @@ -154,6 +160,20 @@ export function boundedIntegerLoopFacts( return result; } +/** Every length an array can have: SCR_ARR_MAX_LENGTH (scr_array.c). */ +export const ARRAY_LENGTH_RANGE: IntegerRange = { min: 0, max: 4294967295 }; + +/** `a.length` of a typed array value (an ordinary JS array). */ +function isArrayLength(e: IrExpr): boolean { + return ( + e.kind === "arrIntrinsic" && + e.method === "length" && + e.args.length === 0 && + e.type.kind === "f64" && + e.receiver.type.kind === "array" + ); +} + /** The most UTF-16 code units a runtime string holds: SCR_STR_MAX_CAP * (scr_runtime.h) caps a string's UTF-8 bytes, and a UTF-16 length never * exceeds the UTF-8 byte length. */ @@ -241,6 +261,9 @@ export function guardedLoopCounterFacts( stable(e.receiver.localId) ) return { min: 0, max: STRING_MAX_UNITS }; + // An array length can change on any pass, but never leaves its range, + // which is all a ceiling needs. + if (isArrayLength(e)) return ARRAY_LENGTH_RANGE; return null; }; // The bounds a condition's conjuncts put on counters while it holds. diff --git a/packages/compiler/src/ir/integer-loops.test.ts b/packages/compiler/src/ir/integer-loops.test.ts index 0f7b9ae13..75968b262 100644 --- a/packages/compiler/src/ir/integer-loops.test.ts +++ b/packages/compiler/src/ir/integer-loops.test.ts @@ -208,3 +208,111 @@ test("a string read at the counter is an integer consumer", () => { s.body = [read(ref("limit"))]; expect(matchIntegerCountedForLoop(s, withText)).toBeNull(); }); + +test("a stable array length bounds a counted loop; a resizing body keeps it a condition", () => { + const ARR = arrayOf(F64); + const array = (localId: string): IrExpr => ({ kind: "varRef", localId, type: ARR, loc }); + const length: IrExpr = { + kind: "arrIntrinsic", + method: "length", + receiver: array("a"), + args: [], + type: F64, + loc, + }; + const read = (index: IrExpr): IrExpr => ({ + kind: "arrIntrinsic", + method: "getNumber", + receiver: array("a"), + args: [index], + type: F64, + loc, + }); + // for (let i = 0; i < a.length; i += 2) a[i]; + const strided = (body: IrStmt[]): IrStmt & { kind: "for" } => { + const s = counted(length); + s.update = { + kind: "assign", + localId: "i", + value: { kind: "bin", op: "+", left: ref("i"), right: number(2), type: F64, loc }, + loc, + }; + s.body = [{ kind: "exprStmt", expr: read(ref("i")), loc }, ...body]; + return s; + }; + const ranges = new Map([[length, { min: 0, max: 4294967295 }]]); + expect(matchIntegerCountedForLoop(strided([]), locals, ranges, new Set())).toMatchObject({ + localId: "i", + guarded: false, + range: { min: 0, max: 4294967294 }, + }); + // Without the alias facts there is no stability proof. + expect(matchIntegerCountedForLoop(strided([]), locals, ranges)).toBeNull(); + const push: IrStmt = { + kind: "exprStmt", + expr: { + kind: "arrIntrinsic", + method: "push", + receiver: array("a"), + args: [number(1)], + type: F64, + loc, + }, + loc, + }; + expect(matchIntegerCountedForLoop(strided([push]), locals, ranges, new Set())).toBeNull(); + // An element write at the counter stays below the length; one past it can grow the array. + const write = (index: IrExpr): IrStmt => ({ + kind: "arraySet", + arr: array("a"), + index, + value: number(0), + loc, + }); + expect( + matchIntegerCountedForLoop(strided([write(ref("i"))]), locals, ranges, new Set()), + ).not.toBeNull(); + const past = { kind: "bin", op: "+", left: ref("i"), right: number(1), type: F64, loc } as const; + expect(matchIntegerCountedForLoop(strided([write(past)]), locals, ranges, new Set())).toBeNull(); +}); + +test("a descending loop from an array's last index takes an integer counter for its reads", () => { + const ARR = arrayOf(F64); + const length: IrExpr = { + kind: "arrIntrinsic", + method: "length", + receiver: { kind: "varRef", localId: "a", type: ARR, loc }, + args: [], + type: F64, + loc, + }; + const start: IrExpr = { kind: "bin", op: "-", left: length, right: number(1), type: F64, loc }; + const s = loop(start); + s.cond = { kind: "bin", op: ">=", left: ref("i"), right: number(0), type: BOOL, loc }; + s.update = { + kind: "assign", + localId: "i", + value: { kind: "bin", op: "-", left: ref("i"), right: number(1), type: F64, loc }, + loc, + }; + s.body = [ + { + kind: "exprStmt", + expr: { + kind: "arrIntrinsic", + method: "getNumber", + receiver: { kind: "varRef", localId: "a", type: ARR, loc }, + args: [ref("i")], + type: F64, + loc, + }, + loc, + }, + ]; + const ranges = new Map([[start, { min: -1, max: 4294967294 }]]); + expect(matchIntegerCountedForLoop(s, locals, ranges)).toMatchObject({ + step: -1, + guarded: false, + range: { min: 0, max: 4294967294 }, + }); +}); diff --git a/packages/compiler/src/ir/integer-loops.ts b/packages/compiler/src/ir/integer-loops.ts index c854a99c5..3ba186376 100644 --- a/packages/compiler/src/ir/integer-loops.ts +++ b/packages/compiler/src/ir/integer-loops.ts @@ -3,6 +3,7 @@ import type { IntegerRange, IntegerRanges } from "./integer-ranges.js"; import { everyExprChild, everyStmtChild, everyStmtList } from "./traverse.js"; import { byteNumberAccess } from "./byte-numbers.js"; +import { preservesArrayLength } from "./array-length-stability.js"; /** A canonical byte loop whose induction variable is mathematically an * unsigned integer for every body entry. Backends may keep this binding in @@ -47,15 +48,17 @@ function constantStep(update: IrStmt | null, id: string): number | null { } /** Pure initial values may be evaluated again on a fallback path. Bounds - * additionally have to remain invariant throughout the loop. Array length - * is allowed only in the initializer; its mutable value is never hoisted - * from a condition. Typed-array lengths are fixed for a given receiver. */ + * additionally have to remain invariant throughout the loop. An array length + * is hoisted from a condition only when `stableLength` proves the loop never + * changes it; otherwise it is allowed only in the initializer. Typed-array + * lengths are fixed for a given receiver. */ function stableLoopValue( value: IrExpr, locals: ReadonlyMap, body: IrStmt[], invariant: boolean, counter: string, + stableLength: (receiver: string) => boolean = () => false, ): boolean { switch (value.kind) { case "numLit": @@ -77,16 +80,16 @@ function stableLoopValue( value.op === "&" || value.op === ">>>" || value.op === "|") && - stableLoopValue(value.left, locals, body, invariant, counter) && - stableLoopValue(value.right, locals, body, invariant, counter) + stableLoopValue(value.left, locals, body, invariant, counter, stableLength) && + stableLoopValue(value.right, locals, body, invariant, counter, stableLength) ); case "bytesIntrinsic": case "arrIntrinsic": return ( (value.method === "length" || (value.kind === "bytesIntrinsic" && value.method === "byteLength")) && - (value.kind === "bytesIntrinsic" || !invariant) && value.receiver.kind === "varRef" && + (value.kind === "bytesIntrinsic" || !invariant || stableLength(value.receiver.localId)) && stableLoopValue(value.receiver, locals, body, invariant, counter) ); default: @@ -103,6 +106,7 @@ export function matchIntegerCountedForLoop( stmt: IrStmt & { kind: "for" }, locals: ReadonlyMap, ranges: IntegerRanges = new Map(), + unaliased: ReadonlySet | null = null, ): IntegerCountedForLoop | null { const init = stmt.init; if (init?.kind !== "varDecl" || !init.init) return null; @@ -135,7 +139,38 @@ export function matchIntegerCountedForLoop( op = ({ "<": ">", "<=": ">=", ">": "<", ">=": "<=" } as Record)[op] ?? ""; } if (step > 0 ? op !== "<" && op !== "<=" : op !== ">" && op !== ">=") return null; - if (!stableLoopValue(limit, locals, stmt.body, true, local.id)) return null; + // An ascending counter compared below `a.length - c` holds an index below + // the length on every pass while that length is unchanged. + const belowLength = (receiver: string): boolean => { + let offset = 0, + length = limit; + if ( + length.kind === "bin" && + length.op === "-" && + length.right.kind === "numLit" && + Number.isFinite(length.right.value) + ) { + offset = length.right.value; + length = length.left; + } + return ( + step > 0 && + length.kind === "arrIntrinsic" && + length.method === "length" && + length.receiver.kind === "varRef" && + length.receiver.localId === receiver && + (op === "<" ? offset >= 0 : offset >= 1) + ); + }; + const stableLength = (receiver: string): boolean => + unaliased !== null && + preservesArrayLength( + [cond, ...stmt.body], + { receiver, inRange: belowLength(receiver) ? local.id : null }, + locals, + unaliased, + ); + if (!stableLoopValue(limit, locals, stmt.body, true, local.id, stableLength)) return null; const inclusive = op === "<=" || op === ">="; const guardLimit = step > 0 ? 2 ** 53 - (inclusive ? 1 : 0) : -(2 ** 53) + (inclusive ? 1 : 0); const boundRange = @@ -178,10 +213,19 @@ export function matchIntegerCountedForLoop( // A string read at the counter indexes with the integer directly. if (e.kind === "strIntrinsic" && e.method === "charCodeAt" && e.args[0] && counter(e.args[0])) useful = true; + // So does an array element read; only a loop that needs no versioned + // copy takes the integer counter for it. + if ( + !guarded && + ((e.kind === "arrayGet" && counter(e.index)) || + (e.kind === "arrIntrinsic" && e.method === "getNumber" && e.args[0] && counter(e.args[0]))) + ) + useful = true; return (!guarded || ++cost <= 80) && everyExprChild(e, expr, bodyStmt); }; const bodyStmt = (s: IrStmt): boolean => { if (s.kind === "bytesSet" && counter(s.index)) useful = true; + if (!guarded && s.kind === "arraySet" && counter(s.index)) useful = true; if ( guarded && (s.kind === "for" || s.kind === "forOf" || s.kind === "while" || s.kind === "doWhile") diff --git a/packages/compiler/src/ir/integer-ranges.test.ts b/packages/compiler/src/ir/integer-ranges.test.ts index e9d1797d1..8e1c97f66 100644 --- a/packages/compiler/src/ir/integer-ranges.test.ts +++ b/packages/compiler/src/ir/integer-ranges.test.ts @@ -334,7 +334,8 @@ test("filtered strided loops retain decoded values and bound secondary cursors", const ranges = analyzeIntegerRanges(f); expect(ranges.get(cursor)).toEqual({ min: 0, max: 80 }); expect(ranges.get(decoded)).toEqual({ min: 0, max: 4294967295 }); - expect(ranges.get(conditionRead)).toEqual({ min: 0, max: 124 }); + // The last pass enters at most at 99, so the failing test reads at most 123. + expect(ranges.get(conditionRead)).toEqual({ min: 0, max: 123 }); for (const mutation of ["fraction", "repeat", "overflow", "counter"] as const) { const changed = structuredClone(f); const body = (changed.body[1] as IrStmt & { kind: "for" }).body; @@ -795,6 +796,77 @@ test("a while condition on a string's length bounds a counter, and guarded incre expect(run((inside) => [observe(inside), bump, rebound]).after).toBeNull(); }); +test("an array length bounds while and for counters even when the body resizes the array", () => { + // let i = 0; while (i < items.length) { observe(i); items.push(i); i++; } observe(i); + const ARR = { kind: "array", elem: F64 } as const; + const items: IrExpr = { loc, kind: "varRef", localId: "items", type: ARR }; + const length: IrExpr = { + loc, + kind: "arrIntrinsic", + method: "length", + receiver: items, + args: [], + type: F64, + }; + const push: IrStmt = { + loc, + kind: "exprStmt", + expr: { + loc, + kind: "arrIntrinsic", + method: "push", + receiver: items, + args: [ref("i")], + type: F64, + }, + }; + const bump: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "incDec", op: "+", prefix: false, localId: "i", type: F64 }, + }; + const inside = ref("i"), + after = ref("i"); + const f = fn([ + { loc, kind: "assign", localId: "i", value: num(0) }, + { + loc, + kind: "while", + cond: condition("<", ref("i"), length), + body: [observe(inside), push, bump], + }, + observe(after), + ]); + f.locals.push( + { id: "i", name: "i", type: F64, mutable: true }, + { id: "items", name: "items", type: ARR, mutable: false }, + ); + const ranges = analyzeIntegerRanges(f); + expect(ranges.get(inside)).toEqual({ min: 0, max: 4294967294 }); + expect(ranges.get(after)).toEqual({ min: 0, max: 4294967295 }); + // A secondary cursor of `for (let j = 0; j < items.length; j++) k++` stays a uint32. + const cursor = ref("k"); + const loop: IrStmt = { + loc, + kind: "for", + init: { loc, kind: "varDecl", localId: "j", init: num(0) }, + cond: condition("<", ref("j"), length), + update: { loc, kind: "assign", localId: "j", value: bin("+", ref("j"), num(1)) }, + body: [ + push, + observe(cursor), + { loc, kind: "assign", localId: "k", value: bin("+", ref("k"), num(1)) }, + ], + }; + const g = fn([{ loc, kind: "assign", localId: "k", value: num(0) }, loop]); + g.locals.push( + { id: "j", name: "j", type: F64, mutable: true }, + { id: "k", name: "k", type: F64, mutable: true }, + { id: "items", name: "items", type: ARR, mutable: false }, + ); + expect(analyzeIntegerRanges(g).get(cursor)).toEqual({ min: 0, max: 4294967295 }); +}); + test("whole-program slot facts seed parameters, field reads and call results", () => { const field: IrExpr = { loc, diff --git a/packages/compiler/src/ir/ir.test.ts b/packages/compiler/src/ir/ir.test.ts index 60ecc9ad0..52ef9b9c1 100644 --- a/packages/compiler/src/ir/ir.test.ts +++ b/packages/compiler/src/ir/ir.test.ts @@ -159,6 +159,17 @@ describe("checked records with opaque payloads", () => { isJsonStringifyDynamicType(arrayOf({ kind: "union", unionId: "optional" }), record, union), ).toBe(true); }); + + test("class instances stringify through the runtime JSON traversal only", () => { + const point: IrType = { kind: "object", className: "Point" }; + const failure: IrType = { kind: "object", className: "%Error" }; + expect(isJsonStringifyDynamicType(point, record, union)).toBe(true); + expect(isJsonStringifyDynamicType(arrayOf(point), record, union)).toBe(true); + expect(isJsonStringifyDynamicType(failure, record, union)).toBe(false); + expect(isJsonStringifySafeType(point, record, union)).toBe(false); + expect(isJsonSafeType(point, record, union)).toBe(false); + expect(canDynCheckTo(arrayOf(point), record, union)).toBe(true); + }); }); describe("native typed-array boundaries", () => { diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 5382a6735..db4beca6b 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1651,11 +1651,27 @@ export interface IrFunction { * out, `nextT` what `.next(v)` sends in (the yield expression's result * type). `async` alongside this field marks an async generator: its * resume methods queue requests and return promises. */ - generator?: { yieldT: IrType; nextT: IrType; resultType: IrType & { kind: "record" } }; + generator?: { + yieldT: IrType; + nextT: IrType; + resultType: IrType & { kind: "record" }; + /** The first parameter is a generator this one reads from (an + * iterator helper's source): `.return()` before the first `.next()` + * closes it. */ + closesSource?: true; + }; /** Source function name for native error stack frames. */ sourceName?: string; /** Ordinary authored function declarations/expressions own a prototype. */ ownsPrototype?: true; + /** The function's execution tier (ir/tiers.ts). Absent means static: the + * function may use dynamic values only through boundary operations + * (dynFrom, dynCheck, kind tests) and moves, which the validator enforces + * (invariant I1). "dynamic" admits the dynamic operation vocabulary + * (dynKeyGet, dynCall, dyn.* builtins, ...). Lowering assigns the field + * from the body (assignLegacyTiers); stage S2 of the two-tier design + * keeps it out of serialized IR, so loaders derive it again. */ + tier?: "dynamic"; body: IrStmt[]; loc: SrcLoc; } @@ -1916,6 +1932,11 @@ export type IrStmt = /** Resource-management cleanup: if the guarded body and cleanup both * throw, raise a SuppressedError instead of replacing the first error. */ suppressFinallyErrors?: true; + /** Iterator closing: if the guarded body threw and the cleanup throws + * too, the cleanup's error is dropped and the first one propagates. + * After a generator .return() the cleanup's error replaces it, and on + * normal exits it propagates. */ + dropFinallyErrors?: true; loc: SrcLoc; }; @@ -3709,9 +3730,10 @@ export function isJsonStringifySafeType( return isJsonSafeAt(t, getRecord, getUnion, true, false, new Set()); } -/** A JSON-shaped container with unknown payloads needs the runtime JSON - * traversal. Its declared structure can be boxed, but payloads may carry - * omitted values, toJSON methods, or cycles. This predicate does not widen +/** A JSON-shaped container with unknown payloads, or a class instance at + * any position, needs the runtime JSON traversal. Its declared structure can + * be boxed, but payloads may carry omitted values, toJSON methods, added + * properties, or cycles. This predicate does not widen * the type-directed serializer, checked-value boundary, or root-undefined * contract; callers must select the checked runtime traversal explicitly. */ export function isJsonStringifyDynamicType( @@ -3720,7 +3742,7 @@ export function isJsonStringifyDynamicType( getUnion: (unionId: string) => IrUnionDef | undefined, ): boolean { return ( - (t.kind === "record" || t.kind === "array" || t.kind === "union") && + (t.kind === "record" || t.kind === "array" || t.kind === "union" || isDynTypedRefType(t)) && isJsonSafeAt(t, getRecord, getUnion, true, false, new Set(), true) && !isJsonStringifySafeType(t, getRecord, getUnion) ); @@ -3830,8 +3852,12 @@ function isJsonSafeAt( return nativeFields && !stringify; case "bytes": return nativeFields; - case "func": + // Class instances stringify through the runtime JSON traversal, which + // reads their own enumerable properties and calls toJSON. case "object": + if (nativeFields && stringify && isDynTypedRefType(t)) return true; + return false; + case "func": // Class values stringify as "{}" husks in Node (own enumerable statics // aside — not representable type-directedly); rejected like Maps. case "classval": @@ -3917,6 +3943,8 @@ export const DYN_HANDLE_KINDS: ReadonlyMap ["http2Stream", { tag: "SCR_DYNH_H2_STREAM", cls: "Http2Stream" }], ["httpClientReq", { tag: "SCR_DYNH_HTTP_CLIENT", cls: "ClientRequest" }], ["dir", { tag: "SCR_DYNH_DIR", cls: "Dir" }], + ["cryptoHash", { tag: "SCR_DYNH_HASH", cls: "Hash" }], + ["cryptoHmac", { tag: "SCR_DYNH_HMAC", cls: "Hmac" }], ]); /** A class value that crosses an `unknown` slot as a compiler-owned typed diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index d16edfabd..7df85c738 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -46,6 +46,8 @@ export interface RuntimeFeatures { } const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ + // DEP0040 rides the dyn-async unit's warning machinery. + "punycode.warn", "fs.callbackValue", "fs.callbackCall", "fs.callbackGlob", diff --git a/packages/compiler/src/ir/serialize.ts b/packages/compiler/src/ir/serialize.ts index 54e210ea3..1d0e5364e 100644 --- a/packages/compiler/src/ir/serialize.ts +++ b/packages/compiler/src/ir/serialize.ts @@ -7,6 +7,7 @@ * text parts and decoded from bytes one function at a time; neither direction * materializes the complete document as a single string. */ import type { IrExpr, IrFunction, IrModule, IrStmt } from "./ir.js"; +import { deriveModuleTiers } from "./tiers.js"; // Version 16 removes embedded-engine types, operations, and module metadata. // Older modules can require runtime support that no longer exists. @@ -42,6 +43,19 @@ function encodeNumber(_key: string, value: unknown): unknown { return value; } +/** Stage S2 of the two-tier design keeps function tiers out of IR + * artifacts, so a report-only release changes no serialized byte; loaders + * derive them again (deriveModuleTiers). No IR node other than a function + * carries a `tier` key. */ +function encodeWithoutTier(key: string, value: unknown): unknown { + return key === "tier" ? undefined : encodeNumber(key, value); +} + +function encodeFunction(fn: IrFunction): string | undefined { + if (fn.tier !== undefined) return JSON.stringify(fn, encodeWithoutTier); + return ordinaryNumbers(fn) ? JSON.stringify(fn) : JSON.stringify(fn, encodeNumber); +} + /** The compact encoding as ordered parts: the module header up to the * function array, one part per function (with separate comma parts), and the * header's remainder. Concatenating the parts yields exactly @@ -58,9 +72,7 @@ export function serializeModuleParts(mod: IrModule): string[] { for (let index = 0; index < mod.functions.length; index++) { const fn = mod.functions[index]!; if (index > 0) parts.push(","); - parts.push( - (ordinaryNumbers(fn) ? JSON.stringify(fn) : JSON.stringify(fn, encodeNumber)) ?? "null", - ); + parts.push(encodeFunction(fn) ?? "null"); } parts.push("]" + header.slice(offset + slot.length)); return parts; @@ -70,7 +82,7 @@ export function serializeModuleParts(mod: IrModule): string[] { * readable default. Both return one string, so modules whose encoding * exceeds the host's string limit must use `serializeModuleParts`. */ export function serializeModule(mod: IrModule, compact = false): string { - if (!compact) return JSON.stringify(mod, encodeNumber, 2); + if (!compact) return JSON.stringify(mod, encodeWithoutTier, 2); return serializeModuleParts(mod).join(""); } @@ -96,7 +108,7 @@ function checkedVersion(mod: IrModule): IrModule { } export function deserializeModule(json: string): IrModule { - return checkedVersion(JSON.parse(json, decodeNumber) as IrModule); + return deriveModuleTiers(checkedVersion(JSON.parse(json, decodeNumber) as IrModule)); } /** A node as it reads back from the compact encoding: a fresh object, as @@ -249,5 +261,5 @@ export function deserializeModuleBytes(bytes: Buffer): IrModule { ); } mod.functions = functions; - return mod; + return deriveModuleTiers(mod); } diff --git a/packages/compiler/src/ir/tiers.test.ts b/packages/compiler/src/ir/tiers.test.ts new file mode 100644 index 000000000..c5f1ad4b8 --- /dev/null +++ b/packages/compiler/src/ir/tiers.test.ts @@ -0,0 +1,142 @@ +import { expect, test } from "vitest"; +import { BOOL, DYN, F64, STRING, VOID, type IrExpr, type IrFunction, type IrModule } from "./ir.js"; +import { + deserializeModule, + deserializeModuleBytes, + serializeModule, + serializeModuleParts, +} from "./serialize.js"; +import { + assignLegacyTiers, + dynamicOperationOf, + firstDynamicOperation, + functionTier, +} from "./tiers.js"; +import { validateModule } from "./validate.js"; + +const loc = { file: "tiers.ts", start: 0, end: 0 }; +const dynParam = { kind: "varRef", localId: "v", type: DYN, loc } as const satisfies IrExpr; + +function fn(name: string, value: IrExpr): IrFunction { + return { + name, + params: [{ localId: "v", name: "v", type: DYN }], + returnType: value.type, + locals: [{ id: "v", name: "v", type: DYN, mutable: false }], + body: [{ kind: "return", value, loc }], + loc, + }; +} + +function moduleOf(...functions: IrFunction[]): IrModule { + const main: IrFunction = { + name: "%main", + params: [], + returnType: VOID, + locals: [], + body: [], + loc, + }; + return { irVersion: 16, sourceFile: "tiers.ts", functions: [main, ...functions], entry: "%main" }; +} + +const keyGet: IrExpr = { + kind: "dynKeyGet", + key: { kind: "strLit", value: "name", type: STRING, loc }, + value: dynParam, + type: DYN, + loc, +}; + +test("dynamic operations and boundary operations are told apart", () => { + expect(dynamicOperationOf(keyGet)).toBe("dynKeyGet"); + expect( + dynamicOperationOf({ + kind: "libCall", + fn: "dyn.add", + args: [dynParam, dynParam], + type: DYN, + loc, + }), + ).toBe("dyn.add"); + expect( + dynamicOperationOf({ + kind: "call", + callee: "%dyn.class.property.12", + args: [dynParam], + type: DYN, + loc, + }), + ).toBe("%dyn.class.property"); + // Boundary operations: conversions and kind tests. + expect(dynamicOperationOf({ kind: "dynCheck", value: dynParam, type: F64, loc })).toBeNull(); + expect( + dynamicOperationOf({ + kind: "dynFrom", + value: { kind: "numLit", value: 1, type: F64, loc }, + type: DYN, + loc, + }), + ).toBeNull(); + expect( + dynamicOperationOf({ kind: "dynTest", test: "string", value: dynParam, type: BOOL, loc }), + ).toBeNull(); + expect( + dynamicOperationOf({ kind: "libCall", fn: "dyn.typedRefIs", args: [dynParam], type: DYN, loc }), + ).toBeNull(); + // Moves and calls of ordinary functions. + expect(dynamicOperationOf(dynParam)).toBeNull(); + expect( + dynamicOperationOf({ kind: "call", callee: "helper", args: [dynParam], type: DYN, loc }), + ).toBeNull(); +}); + +test("lowering's classification puts exactly the functions with dynamic operations in the dynamic tier", () => { + const reader = fn("reader", keyGet); + const mover = fn("mover", dynParam); + const dynamic = assignLegacyTiers([reader, mover]); + expect(dynamic.map((d) => [d.name, d.operation])).toEqual([["reader", "dynKeyGet"]]); + expect(functionTier(reader)).toBe("dynamic"); + expect(functionTier(mover)).toBe("static"); + expect(firstDynamicOperation(mover)).toBeNull(); +}); + +test("the validator enforces I1: static functions perform no dynamic operation", () => { + const reader = fn("reader", keyGet); + const errors = validateModule(moduleOf(reader)); + expect(errors.map((e) => e.message)).toEqual([ + "in reader: static-tier function performs the dynamic operation dynKeyGet", + ]); + reader.tier = "dynamic"; + expect(validateModule(moduleOf(reader))).toEqual([]); + // A logical operator over dynamic values applies ToBoolean to them. + const either = fn("either", { + kind: "logical", + op: "||", + left: dynParam, + right: dynParam, + type: DYN, + loc, + }); + expect(validateModule(moduleOf(either)).map((e) => e.message)).toEqual([ + "in either: static-tier function performs the dynamic operation logical", + ]); +}); + +test("serialized IR carries no tiers in stage S2, and loading derives them again", () => { + const reader = fn("reader", keyGet); + const mod = moduleOf(reader); + const untiered = serializeModule(mod, true); + assignLegacyTiers(mod.functions); + expect(reader.tier).toBe("dynamic"); + expect(serializeModule(mod, true)).toBe(untiered); + expect(serializeModuleParts(mod).join("")).toBe(untiered); + expect(serializeModule(mod)).not.toContain('"tier"'); + for (const loaded of [ + deserializeModule(untiered), + deserializeModuleBytes(Buffer.from(untiered)), + ]) { + expect(loaded.functions.map(functionTier)).toEqual(["static", "dynamic"]); + expect(validateModule(loaded)).toEqual([]); + } +}); diff --git a/packages/compiler/src/ir/tiers.ts b/packages/compiler/src/ir/tiers.ts new file mode 100644 index 000000000..201bc2a58 --- /dev/null +++ b/packages/compiler/src/ir/tiers.ts @@ -0,0 +1,157 @@ +/* Execution tiers (two-tier design, stage S2: report-only). + * + * Every IR function belongs to one of two tiers: + * + * - static: today's compiler. Exact layouts, direct calls, unboxed values. + * A static function may hold dynamic (`dyn`) values, pass them to calls, + * return them and store them, and it may cross the boundary with the + * boundary operations below, but it never operates on a dynamic value. + * - dynamic: code that operates on dynamic values with JavaScript semantics + * (property reads and writes, generic operators, calls of dynamic values, + * dynamic object and array construction). + * + * Invariant I1 (validate.ts): a static function contains no dynamic + * operation. Only boundary operations and moves may mix the tiers. Calls + * of generated `%dyn.*` helpers count as dynamic operations: they are the + * dynamic dispatch the lowering placed out of line. Calls of other dynamic + * functions are boundaries (the design's "call to a dynamic function"). + * + * Boundary operations: + * - `dynFrom` (static to dynamic, the design's `toJs`) and `caughtToDyn`; + * - `dynCheck` (dynamic to static, the design's `fromJs`); + * - kind tests that only inspect a dynamic value's tag (`dynTest`, the + * `dyn.*Is` and `dyn.typeof` builtins): the tag test of a conversion; + * - calls of non-`dyn.*` builtins whose signatures take or return `dyn` + * (typed APIs implemented through dyn, leak L6 of the design). They stay + * boundaries until stage S1 gives them typed signatures. + * + * Classification of existing code. The `dyn` vocabulary is today's + * dynamic tier (`ScrDyn`): `any`, untyped JavaScript, unschematized + * `JSON.parse`, and the generated dynamic dispatch helpers already lower to + * it. Lowering therefore assigns tiers from function bodies: a function + * containing a dynamic operation is dynamic, every other function is static + * (`assignLegacyTiers`). Demotion (stage S6) will add tier decisions that + * do not follow from a body; until then the classification is exact, and + * the validator guards it against later passes and hand-written IR. + * + * Serialization. Stage S2 must not change any artifact, so serialized IR + * omits `tier` (serialize.ts) and loaders derive it again with the same + * classification. The next IR version serializes the field. */ +import type { IrExpr, IrFunction, IrModule, SrcLoc } from "./ir.js"; +import { everyStmtList, type IrVisitor } from "./traverse.js"; + +export type IrTier = "static" | "dynamic"; + +export function functionTier(fn: IrFunction): IrTier { + return fn.tier === "dynamic" ? "dynamic" : "static"; +} + +/** `dyn.*` builtins that only inspect a dynamic value's kind: the tag test + * half of a dynamic-to-static conversion. */ +const DYN_KIND_TESTS: ReadonlySet = new Set([ + "dyn.typedRefIs", + "dyn.typedRefKeyIs", + "dyn.classIs", + "dyn.nativeSetIs", + "dyn.nativeMapIs", + "dyn.nativeUrlIs", + "dyn.nativeDateIs", + "dyn.nativeRegexIs", + "dyn.dataViewIs", + "dyn.typeof", +]); + +const DYN_HELPER_PREFIX = "%dyn."; + +/** `%dyn.class.property.12` → `%dyn.class.property`. */ +function dynHelperFamily(name: string): string { + return name.replace(/(\.\d+)+$/, ""); +} + +/** The dynamic operation an expression performs, named for reports + * (`dynKeyGet`, `dyn.keySet`, ...), or null for static operations, boundary + * operations and moves. Only the node itself is inspected. */ +export function dynamicOperationOf(e: IrExpr): string | null { + switch (e.kind) { + case "dynCall": + case "dynInvoke": + case "dynObjLit": + case "dynArrLit": + case "dynKeyGet": + case "dynHasKey": + case "dynScalarEq": + case "dynDestrCheck": + case "dynIterN": + return e.kind; + case "logical": + // `a || b` over dyn operands applies ToBoolean to a dynamic value. + return e.type.kind === "dyn" ? "logical" : null; + case "libCall": + return e.fn.startsWith("dyn.") && !DYN_KIND_TESTS.has(e.fn) ? e.fn : null; + case "call": + // Generated `%dyn.*` helpers are dynamic operations lowered out of + // line (class dynamic dispatch, dynamic HOF adapters). + return e.callee.startsWith(DYN_HELPER_PREFIX) ? dynHelperFamily(e.callee) : null; + default: + return null; + } +} + +export interface DynamicOperationSite { + operation: string; + loc: SrcLoc; +} + +function collectDynamicOperations(fn: IrFunction, first: boolean): DynamicOperationSite[] { + const found: DynamicOperationSite[] = []; + const visitor: IrVisitor = { + expr: (e) => { + const operation = dynamicOperationOf(e); + if (operation === null) return true; + found.push({ operation, loc: e.loc }); + return !first; + }, + stmt: () => true, + }; + everyStmtList(fn.body, visitor); + return found; +} + +/** The first dynamic operation in a function body, in traversal order. */ +export function firstDynamicOperation(fn: IrFunction): DynamicOperationSite | null { + return collectDynamicOperations(fn, true)[0] ?? null; +} + +/** Every dynamic operation in a function body (coverage reports). */ +export function dynamicOperationSites(fn: IrFunction): DynamicOperationSite[] { + return collectDynamicOperations(fn, false); +} + +/** A function the classification placed in the dynamic tier, with the + * operation that put it there. */ +export interface DynamicFunction { + name: string; + loc: SrcLoc; + operation: string; + operationLoc: SrcLoc; +} + +/** Assign every function its tier from its body (see the header). Returns + * the dynamic functions in module order. */ +export function assignLegacyTiers(functions: readonly IrFunction[]): DynamicFunction[] { + const dynamic: DynamicFunction[] = []; + for (const fn of functions) { + const site = firstDynamicOperation(fn); + if (site === null) continue; + fn.tier = "dynamic"; + dynamic.push({ name: fn.name, loc: fn.loc, operation: site.operation, operationLoc: site.loc }); + } + return dynamic; +} + +/** Loaders call this on deserialized modules (stage S2 serializes no + * tiers). */ +export function deriveModuleTiers(mod: IrModule): IrModule { + assignLegacyTiers(mod.functions); + return mod; +} diff --git a/packages/compiler/src/ir/validate.test.ts b/packages/compiler/src/ir/validate.test.ts index b7caf8c41..8086cc176 100644 --- a/packages/compiler/src/ir/validate.test.ts +++ b/packages/compiler/src/ir/validate.test.ts @@ -20,6 +20,7 @@ import { type IrUnionDef, } from "./ir.js"; import { deserializeModule, serializeModule } from "./serialize.js"; +import { assignLegacyTiers } from "./tiers.js"; import { validateModule } from "./validate.js"; const loc = { file: "numeric-read.ts", start: 0, end: 0 }; @@ -399,7 +400,7 @@ test.each(["sortPrimitive", "toSortedPrimitive"] as const)( ); function expressionModule(expr: IrExpr, unions: IrUnionDef[]): IrModule { - return { + const mod: IrModule = { irVersion: 16, sourceFile: loc.file, entry: "main", @@ -415,6 +416,9 @@ function expressionModule(expr: IrExpr, unions: IrUnionDef[]): IrModule { }, ], }; + // Lowering assigns tiers from function bodies (ir/tiers.ts). + assignLegacyTiers(mod.functions); + return mod; } test("numeric byte tokens and DataView stores validate their storage before emission", () => { diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index d10160115..09ab7d951 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -59,6 +59,7 @@ import { import { DYN_CLASS_PROPERTIES } from "./ir.js"; import { unionWideningTags } from "./analysis.js"; import { alwaysReturns } from "./control-flow.js"; +import { dynamicOperationOf, functionTier } from "./tiers.js"; import { LIB_FN_SIGS } from "./builtin-signatures.js"; /** The promisified crypto calls' value kinds besides checked-dynamic. */ @@ -1019,6 +1020,14 @@ function validateFunction( const locals = new Map(fn.locals.map((l) => [l.id, l])); const err = (message: string, loc: SrcLoc) => errors.push({ message: `in ${fn.name}: ${message}`, loc }); + // Invariant I1 (ir/tiers.ts): a static function reaches dynamic values + // only through boundary operations and moves. + const staticTier = functionTier(fn) === "static"; + const checkTier = (e: IrExpr): void => { + const operation = dynamicOperationOf(e); + if (operation !== null) + err(`static-tier function performs the dynamic operation ${operation}`, e.loc); + }; const asyncCaches = [ ["asyncCacheGlobal", fn.asyncCacheGlobal], @@ -1263,6 +1272,8 @@ function validateFunction( continue; } if (e.kind === "logical") { + // checkExpr already checked the root's tier. + if (staticTier && e !== root) checkTier(e); if ( e.type.kind !== "f64" && e.type.kind !== "string" && @@ -1306,6 +1317,7 @@ function validateFunction( } function checkExpr(e: IrExpr): void { + if (staticTier) checkTier(e); switch (e.kind) { case "numLit": case "strLit": diff --git a/packages/compiler/src/library/prepare.ts b/packages/compiler/src/library/prepare.ts index 94f20198a..298bb43bb 100644 --- a/packages/compiler/src/library/prepare.ts +++ b/packages/compiler/src/library/prepare.ts @@ -553,6 +553,8 @@ export function prepareLibrary( try { lowered = fe.lower({ targetPlatform: buildPlatform, + // Library artifacts are static-tier only (two-tier design I2). + tierMode: "static-only", ...(cbImports.length > 0 ? { ffiImports: cbImports } : {}), ...(cbImports.length > 0 ? { libraryCallbacks: true } : {}), // The profile-mapped exports are called from OUTSIDE the graph: diff --git a/packages/compiler/src/wasm/binary.test.ts b/packages/compiler/src/wasm/binary.test.ts new file mode 100644 index 000000000..5f95f91d2 --- /dev/null +++ b/packages/compiler/src/wasm/binary.test.ts @@ -0,0 +1,244 @@ +import { describe, expect, test } from "vitest"; +import { analyzeWasm, isTranslatableFeature, isWasmHeader } from "./binary.js"; + +/** V8's verdict (the compiler's type world declares no WebAssembly value). */ +const v8Validate = (bytes: Uint8Array): boolean => + ( + globalThis as unknown as { WebAssembly: { validate(bytes: Uint8Array): boolean } } + ).WebAssembly.validate(bytes); + +// A minimal module assembler: enough structure to aim at one decoder rule +// per test. Every module is also checked against V8 (Node is the oracle). + +function leb(n: number): number[] { + const out: number[] = []; + do { + let b = n % 128; + n = Math.floor(n / 128); + if (n > 0) b |= 0x80; + out.push(b); + } while (n > 0); + return out; +} + +function name(text: string): number[] { + const bytes = [...new TextEncoder().encode(text)]; + return [...leb(bytes.length), ...bytes]; +} + +function vec(items: number[][]): number[] { + return [...leb(items.length), ...items.flat()]; +} + +function section(id: number, body: number[]): number[] { + return [id, ...leb(body.length), ...body]; +} + +function module(...sections: number[][]): Uint8Array { + return new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, ...sections.flat()]); +} + +const I32 = 0x7f; +const I64 = 0x7e; +const F32 = 0x7d; +const V128 = 0x7b; +const EXTERNREF = 0x6f; + +const funcType = (params: number[], results: number[]): number[] => [ + 0x60, + ...vec(params.map((p) => [p])), + ...vec(results.map((r) => [r])), +]; +const types = (...fts: number[][]): number[] => section(1, vec(fts)); +const funcs = (...typeIndices: number[]): number[] => + section(3, vec(typeIndices.map((t) => leb(t)))); +const body = (code: number[], locals: number[][] = []): number[] => { + const content = [...vec(locals), ...code, 0x0b]; + return [...leb(content.length), ...content]; +}; +const code = (...bodies: number[][]): number[] => section(10, vec(bodies)); +const memory = (...limits: number[][]): number[] => section(5, vec(limits)); +const exportsOf = (...entries: number[][]): number[] => section(7, vec(entries)); + +function check(bytes: Uint8Array) { + const summary = analyzeWasm(bytes); + const accepted = summary.status === "valid" || summary.status === "unsupported"; + expect(accepted, summary.error ?? "").toBe(v8Validate(bytes)); + return summary; +} + +describe("analyzeWasm", () => { + test("an empty module is valid and uses no proposal", () => { + const s = check(module()); + expect(s).toMatchObject({ status: "valid", error: null, features: [], functions: 0, size: 8 }); + expect(isWasmHeader(module())).toBe(true); + }); + + test("counts functions, imports, exports, memories, tables and globals", () => { + const s = check( + module( + types(funcType([I32, I32], [I32]), funcType([], [])), + section( + 2, + vec([ + [...name("env"), ...name("log"), 0x00, 0x01], + [...name("wasi_snapshot_preview1"), ...name("proc_exit"), 0x00, 0x01], + ]), + ), + funcs(0), + section(4, vec([[0x70, 0x00, 0x01]])), + memory([0x00, 0x01]), + section(6, vec([[I32, 0x01, 0x41, 0x00, 0x0b]])), + exportsOf([...name("add"), 0x00, 0x02], [...name("memory"), 0x02, 0x00]), + code(body([0x20, 0x00, 0x20, 0x01, 0x6a])), + ), + ); + expect(s).toMatchObject({ + status: "valid", + functions: 1, + imports: 2, + exports: 2, + importModules: ["env", "wasi_snapshot_preview1"], + memories: 1, + tables: 1, + globals: 1, + }); + }); + + test.each([ + [ + "sign-extension", + module(types(funcType([I32], [I32])), funcs(0), code(body([0x20, 0x00, 0xc0]))), + ], + [ + "nontrapping-fptoint", + module(types(funcType([F32], [I32])), funcs(0), code(body([0x20, 0x00, 0xfc, 0x00]))), + ], + [ + "bulk-memory", + module( + types(funcType([], [])), + funcs(0), + memory([0x00, 0x01]), + code(body([0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xfc, 0x0b, 0x00])), + ), + ], + [ + "multi-value", + module(types(funcType([], [I32, I64])), funcs(0), code(body([0x41, 0x01, 0x42, 0x02]))), + ], + ["reference-types", module(section(4, vec([[EXTERNREF, 0x00, 0x01]])))], + ["extended-const", module(section(6, vec([[I32, 0x00, 0x41, 0x01, 0x41, 0x02, 0x6a, 0x0b]])))], + [ + "mutable-globals", + module(section(2, vec([[...name("env"), ...name("sp"), 0x03, I32, 0x01]]))), + ], + ])("detects %s and stays translatable", (feature, bytes) => { + const s = check(bytes); + expect(s.status).toBe("valid"); + expect(s.features).toContain(feature); + expect(isTranslatableFeature(feature)).toBe(true); + }); + + test.each([ + [ + "simd", + module( + types(funcType([], [V128])), + funcs(0), + code(body([0xfd, 0x0c, ...Array.from({ length: 16 }, () => 0)])), + ), + ], + ["threads", module(memory([0x03, 0x01, 0x01]))], + ["exceptions", module(types(funcType([], [])), section(13, vec([[0x00, 0x00]])))], + ["tail-call", module(types(funcType([], [])), funcs(0), code(body([0x12, 0x00])))], + ["memory64", module(memory([0x04, 0x01]))], + ["multi-memory", module(memory([0x00, 0x01], [0x00, 0x01]))], + ["gc", module(section(1, vec([[0x5f, 0x00]])))], + ])("reports %s as outside the translatable set", (feature, bytes) => { + const s = check(bytes); + expect(s.status).toBe("unsupported"); + expect(s.unsupported).toEqual([feature]); + expect(isTranslatableFeature(feature)).toBe(false); + }); + + test.each([ + ["bad magic", new Uint8Array([0x00, 0x61, 0x73, 0x6e, 1, 0, 0, 0]), "malformed", "magic"], + ["bad version", new Uint8Array([0x00, 0x61, 0x73, 0x6d, 2, 0, 0, 0]), "malformed", "version"], + ["sections out of order", module(funcs(), types()), "malformed", "unexpected section"], + ["section size mismatch", module([0x01, 0x02, 0x00, 0x00]), "malformed", "section size"], + [ + "overlong integer", + module([0x01, 0x06, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]), + "malformed", + "integer", + ], + ["invalid UTF-8 name", module(exportsOf([0x01, 0xff, 0x00, 0x00])), "malformed", "UTF-8"], + [ + "function and code counts differ", + module(types(funcType([], [])), funcs(0, 0), code(body([]))), + "malformed", + "inconsistent", + ], + [ + "illegal opcode", + module(types(funcType([], [])), funcs(0), code(body([0xff]))), + "malformed", + "illegal opcode", + ], + [ + "memory.init without a data count section", + module( + types(funcType([], [])), + funcs(0), + memory([0x00, 0x01]), + code(body([0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xfc, 0x08, 0x00, 0x00])), + ), + "malformed", + "data count", + ], + [ + "type mismatch", + module(types(funcType([], [I32])), funcs(0), code(body([0x42, 0x00]))), + "invalid", + "type mismatch", + ], + [ + "unknown function", + module(types(funcType([], [])), funcs(0), code(body([0x10, 0x05]))), + "invalid", + "unknown function", + ], + [ + "duplicate export", + module( + types(funcType([], [])), + funcs(0), + exportsOf([...name("f"), 0x00, 0x00], [...name("f"), 0x00, 0x00]), + code(body([])), + ), + "invalid", + "duplicate export", + ], + ["memory above 4 GiB", module(memory([0x00, ...leb(65537)])), "invalid", "65536 pages"], + [ + "undeclared function reference", + module(types(funcType([], [])), funcs(0), code(body([0xd2, 0x00, 0x1a]))), + "invalid", + "undeclared", + ], + ])("rejects %s", (_label, bytes, status, message) => { + const s = check(bytes); + expect(s.status).toBe(status); + expect(s.error).toContain(message); + }); + + test("never throws on truncated input", () => { + const whole = module( + types(funcType([I32, I32], [I32])), + funcs(0), + code(body([0x20, 0x00, 0x20, 0x01, 0x6a])), + ); + for (let n = 0; n < whole.length; n++) check(whole.subarray(0, n)); + }); +}); diff --git a/packages/compiler/src/wasm/binary.ts b/packages/compiler/src/wasm/binary.ts new file mode 100644 index 000000000..5ddc28cc9 --- /dev/null +++ b/packages/compiler/src/wasm/binary.ts @@ -0,0 +1,1957 @@ +/* The WebAssembly binary decoder and validator. + * + * Decodes a module completely and validates it against the feature level + * scriptc means to translate to native code: the MVP plus the finished + * post-MVP proposals that real npm modules use (sign-extension operators, + * non-trapping float-to-int conversions, bulk memory, multi-value, mutable + * globals, reference types and extended constant expressions). Where the + * WebAssembly 3.0 specification and V8 relax a 2.0 rule inside that set (a + * global initializer may read any earlier immutable global), the decoder + * follows them, because Node is the behavior oracle. + * + * Modules that use a proposal outside that set (SIMD, threads, exception + * handling, tail calls, function references and GC, memory64, multiple + * memories) still decode: the instructions that need the proposal are read + * for their immediates but not type-checked, and the summary names the + * proposal so that the module can be reported as not translatable rather + * than as invalid. Only GC type definitions stop the decode, since their + * layout would need the full GC type system. + * + * 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. */ + +/** Proposals the summary can name, in report order. The first group is the + * translatable set; the rest are reported as unsupported. */ +export const WASM_FEATURES = [ + "mutable-globals", + "sign-extension", + "nontrapping-fptoint", + "bulk-memory", + "multi-value", + "reference-types", + "extended-const", + "simd", + "threads", + "exceptions", + "tail-call", + "function-references", + "gc", + "memory64", + "multi-memory", +] as const; +export type WasmFeature = (typeof WASM_FEATURES)[number]; + +const TRANSLATABLE: ReadonlySet = new Set([ + "mutable-globals", + "sign-extension", + "nontrapping-fptoint", + "bulk-memory", + "multi-value", + "reference-types", + "extended-const", +]); + +/** True for proposals inside the translatable feature level. */ +export function isTranslatableFeature(feature: string): boolean { + return TRANSLATABLE.has(feature); +} + +/** What a decoded module reports. `status` is "valid" when the module + * validates within the translatable feature level, "unsupported" when it + * uses a proposal outside it (and is otherwise well-formed as far as the + * decoder checks), and "malformed" or "invalid" for a module that every + * engine rejects; `error` then says where and why. */ +export interface WasmModuleSummary { + size: number; + status: "valid" | "unsupported" | "malformed" | "invalid"; + error: string | null; + features: string[]; + unsupported: string[]; + functions: number; + imports: number; + exports: number; + importModules: string[]; + memories: number; + tables: number; + globals: number; + customSections: string[]; +} + +/** A decode failure: malformed bytes or an invalid module. */ +export class WasmError extends Error { + kind: "malformed" | "invalid"; + offset: number; + constructor(kind: "malformed" | "invalid", message: string, offset: number) { + super(message); + this.kind = kind; + this.offset = offset; + } +} + +// Value types. Zero is the "unknown" type of unreachable code. +const UNKNOWN = 0; +const I32 = 0x7f; +const I64 = 0x7e; +const F32 = 0x7d; +const F64 = 0x7c; +const V128 = 0x7b; +const FUNCREF = 0x70; +const EXTERNREF = 0x6f; + +// V8's implementation limits that are part of validation (wasm-limits.h). +const MAX_TYPES = 1000000; +const MAX_FUNCTIONS = 1000000; +const MAX_IMPORTS = 100000; +const MAX_EXPORTS = 100000; +const MAX_GLOBALS = 1000000; +const MAX_DATA_SEGMENTS = 100000; +const MAX_LOCALS = 50000; +const MAX_PARAMS = 1000; +const MAX_RESULTS = 1000; +const MAX_FUNCTION_SIZE = 7654321; +const MAX_MEMORY_PAGES = 65536; +const MAX_TABLE_INIT = 10000000; + +interface FuncType { + params: number[]; + results: number[]; +} + +interface Global { + type: number; + mutable: boolean; + imported: boolean; +} + +interface Table { + type: number; +} + +interface Frame { + opcode: number; + start: number[]; + end: number[]; + height: number; + unreachable: boolean; + /** An `if` that has not seen its `else`. */ + hasElse: boolean; +} + +class Reader { + bytes: Uint8Array; + pos: number; + end: number; + constructor(bytes: Uint8Array, pos: number, end: number) { + this.bytes = bytes; + this.pos = pos; + this.end = end; + } + + malformed(message: string): never { + throw new WasmError("malformed", message, this.pos); + } + + eof(): boolean { + return this.pos >= this.end; + } + + u8(): number { + if (this.pos >= this.end) this.malformed("unexpected end"); + const b = this.bytes[this.pos]!; + this.pos++; + return b; + } + + peek(): number { + if (this.pos >= this.end) this.malformed("unexpected end"); + return this.bytes[this.pos]!; + } + + skip(n: number): void { + if (n > this.end - this.pos) this.malformed("unexpected end"); + this.pos += n; + } + + u32(): number { + let result = 0; + let scale = 1; + for (let i = 0; i < 5; i++) { + const b = this.u8(); + if (i === 4 && (b & 0xf0) !== 0) { + this.malformed(b & 0x80 ? "integer representation too long" : "integer too large"); + } + result += (b & 0x7f) * scale; + if ((b & 0x80) === 0) return result; + scale *= 128; + } + return this.malformed("integer representation too long"); + } + + /** A signed LEB128 of `bits` bits (32, 33 or 64); answers the value for + * 32 and 33 bits and 0 for 64 (no caller needs it). */ + sleb(bits: number): number { + const maxBytes = Math.ceil(bits / 7); + let result = 0; + let scale = 1; + for (let i = 0; i < maxBytes; i++) { + const b = this.u8(); + if (i === maxBytes - 1) { + // The final byte's bits above the value's width must repeat its + // sign bit, and it may not continue. + if (b & 0x80) this.malformed("integer representation too long"); + const used = bits - 7 * i; + const unused = (b >> (used - 1)) & ((1 << (8 - used)) - 1) & 0x7f; + const all = (1 << (7 - used + 1)) - 1; + if (unused !== 0 && unused !== all) this.malformed("integer too large"); + } + if (bits < 64) result += (b & 0x7f) * scale; + scale *= 128; + if ((b & 0x80) === 0) { + if (bits < 64 && b & 0x40) result -= scale; + return bits < 64 ? result : 0; + } + } + return this.malformed("integer representation too long"); + } + + u64(): void { + this.sleb(64); + } + + name(): string { + const length = this.u32(); + if (length > this.end - this.pos) this.malformed("length out of bounds"); + const start = this.pos; + this.pos += length; + return utf8(this.bytes, start, this.pos, this); + } +} + +function utf8(bytes: Uint8Array, start: number, end: number, reader: Reader): string { + let out = ""; + let i = start; + while (i < end) { + const b = bytes[i]!; + let cp = 0; + let need = 0; + if (b < 0x80) { + cp = b; + } else if (b >= 0xc2 && b <= 0xdf) { + cp = b & 0x1f; + need = 1; + } else if (b >= 0xe0 && b <= 0xef) { + cp = b & 0x0f; + need = 2; + } else if (b >= 0xf0 && b <= 0xf4) { + cp = b & 0x07; + need = 3; + } else { + reader.malformed("malformed UTF-8 encoding"); + } + if (i + need >= end) reader.malformed("malformed UTF-8 encoding"); + for (let k = 1; k <= need; k++) { + const c = bytes[i + k]!; + if ((c & 0xc0) !== 0x80) reader.malformed("malformed UTF-8 encoding"); + cp = cp * 64 + (c & 0x3f); + } + if ( + (need === 2 && (cp < 0x800 || (cp >= 0xd800 && cp <= 0xdfff))) || + (need === 3 && (cp < 0x10000 || cp > 0x10ffff)) + ) { + reader.malformed("malformed UTF-8 encoding"); + } + out += String.fromCodePoint(cp); + i += need + 1; + } + return out; +} + +/** Load and store natural alignments (log2) by opcode, 0x28..0x3e. */ +const MEMORY_ALIGN = [2, 3, 2, 3, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 2, 3, 2, 3, 0, 1, 0, 1, 2]; +/** Load result / store operand types by opcode, 0x28..0x3e. */ +const MEMORY_TYPE = [ + I32, + I64, + F32, + F64, + I32, + I32, + I32, + I32, + I64, + I64, + I64, + I64, + I64, + I64, + I32, + I64, + F32, + F64, + I32, + I32, + I64, + I64, + I64, +]; + +/** Numeric instruction signatures, 0x45..0xc4: operand type, operand + * count, result type. */ +function numericSignature(op: number): [number, number, number] { + if (op === 0x45) return [I32, 1, I32]; + if (op <= 0x4f) return [I32, 2, I32]; + if (op === 0x50) return [I64, 1, I32]; + if (op <= 0x5a) return [I64, 2, I32]; + if (op <= 0x60) return [F32, 2, I32]; + if (op <= 0x66) return [F64, 2, I32]; + if (op <= 0x69) return [I32, 1, I32]; + if (op <= 0x78) return [I32, 2, I32]; + if (op <= 0x7b) return [I64, 1, I64]; + if (op <= 0x8a) return [I64, 2, I64]; + if (op <= 0x91) return [F32, 1, F32]; + if (op <= 0x98) return [F32, 2, F32]; + if (op <= 0x9f) return [F64, 1, F64]; + if (op <= 0xa6) return [F64, 2, F64]; + switch (op) { + case 0xa7: + return [I64, 1, I32]; + case 0xa8: + case 0xa9: + return [F32, 1, I32]; + case 0xaa: + case 0xab: + return [F64, 1, I32]; + case 0xac: + case 0xad: + return [I32, 1, I64]; + case 0xae: + case 0xaf: + return [F32, 1, I64]; + case 0xb0: + case 0xb1: + return [F64, 1, I64]; + case 0xb2: + case 0xb3: + return [I32, 1, F32]; + case 0xb4: + case 0xb5: + return [I64, 1, F32]; + case 0xb6: + return [F64, 1, F32]; + case 0xb7: + case 0xb8: + return [I32, 1, F64]; + case 0xb9: + case 0xba: + return [I64, 1, F64]; + case 0xbb: + return [F32, 1, F64]; + case 0xbc: + return [F32, 1, I32]; + case 0xbd: + return [F64, 1, I64]; + case 0xbe: + return [I32, 1, F32]; + case 0xbf: + return [I64, 1, F64]; + case 0xc0: + case 0xc1: + return [I32, 1, I32]; + default: + return [I64, 1, I64]; + } +} + +const SECTION_ORDER: Record = { + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 13: 6, + 6: 7, + 7: 8, + 8: 9, + 9: 10, + 12: 11, + 10: 12, + 11: 13, +}; + +class ModuleDecoder { + bytes: Uint8Array; + features = new Set(); + types: FuncType[] = []; + funcs: number[] = []; + importedFuncs = 0; + tables: Table[] = []; + memories = 0; + memory64 = false; + globals: Global[] = []; + importedGlobals = 0; + tags = 0; + elemTypes: number[] = []; + dataCount: number | null = null; + dataSegments = 0; + declaredRefs = new Set(); + exportNames = new Set(); + importModules: string[] = []; + customSections: string[] = []; + imports = 0; + exports = 0; + codeCount = 0; + sawCode = false; + /** A type definition outside the function-type subset stopped typing. */ + gcTypes = false; + + constructor(bytes: Uint8Array) { + this.bytes = bytes; + } + + invalid(message: string, offset: number): never { + throw new WasmError("invalid", message, offset); + } + + feature(name: WasmFeature): void { + this.features.add(name); + } + + // ── types ──────────────────────────────────────────────────────────── + + /** A value type; GC and exception-handling reference types are named as + * features and answer UNKNOWN (the code that uses them is not typed). */ + valType(r: Reader): number { + const b = r.u8(); + switch (b) { + case I32: + case I64: + case F32: + case F64: + return b; + case V128: + this.feature("simd"); + return V128; + case FUNCREF: + case EXTERNREF: + return b; + default: + return this.otherRefType(r, b); + } + } + + otherRefType(r: Reader, b: number): number { + if (b === 0x63 || b === 0x64) { + // (ref null ht) / (ref ht): a heap type follows. + this.feature("function-references"); + this.heapType(r); + return UNKNOWN; + } + if (b === 0x69 || b === 0x68 || b === 0x74 || b === 0x75) { + this.feature("exceptions"); + return UNKNOWN; + } + if (b >= 0x6a && b <= 0x73) { + // anyref, eqref, i31ref, structref, arrayref, nullref, ... + this.feature("gc"); + return UNKNOWN; + } + return r.malformed("malformed value type"); + } + + heapType(r: Reader): void { + const b = r.peek(); + if (b >= 0x60 && b <= 0x7f) { + r.u8(); + if (b === 0x69 || b === 0x74) this.feature("exceptions"); + else if (b !== FUNCREF && b !== EXTERNREF) this.feature("gc"); + return; + } + // A concrete heap type: a typed function reference. + const offset = r.pos; + const index = r.sleb(33); + if (index < 0) r.malformed("malformed heap type"); + this.feature("function-references"); + if (index >= this.types.length && !this.gcTypes) this.invalid(`unknown type ${index}`, offset); + } + + refType(r: Reader): number { + const b = r.u8(); + if (b === FUNCREF || b === EXTERNREF) return b; + if (b >= 0x63 && b <= 0x75 && b !== 0x67 && b !== 0x66 && b !== 0x65) { + this.otherRefType(r, b); + return UNKNOWN; + } + return r.malformed("malformed reference type"); + } + + typeSection(r: Reader): void { + const count = r.u32(); + if (count > MAX_TYPES) r.malformed("too many types"); + for (let i = 0; i < count; i++) { + const form = r.u8(); + if (form === 0x60) { + this.types.push(this.funcType(r)); + continue; + } + if (form === 0x4e || form === 0x50 || form === 0x4f || form === 0x5f || form === 0x5e) { + // Recursive groups, subtypes, structs and arrays: the GC type + // system. Stop decoding types; nothing after them can be typed. + this.feature("gc"); + this.gcTypes = true; + r.pos = r.end; + return; + } + r.malformed("malformed function type"); + } + } + + funcType(r: Reader): FuncType { + const params: number[] = []; + const nParams = r.u32(); + if (nParams > MAX_PARAMS) r.malformed("too many parameters"); + for (let i = 0; i < nParams; i++) params.push(this.valType(r)); + const results: number[] = []; + const nResults = r.u32(); + if (nResults > MAX_RESULTS) r.malformed("too many results"); + for (let i = 0; i < nResults; i++) results.push(this.valType(r)); + if (results.length > 1) this.feature("multi-value"); + return { params, results }; + } + + funcTypeAt(index: number, offset: number): FuncType { + if (index >= this.types.length) { + if (this.gcTypes) return { params: [], results: [] }; + this.invalid(`unknown type ${index}`, offset); + } + return this.types[index]!; + } + + limits(r: Reader, kind: "memory" | "table"): { min: number; max: number | null } { + const flags = r.u8(); + if (kind === "memory") { + if (flags > 0x07) r.malformed("malformed limits flags"); + if (flags & 0x02) this.feature("threads"); + if (flags & 0x04) { + this.feature("memory64"); + this.memory64 = true; + } + } else { + // Tables: a maximum flag and the table64 flag; no shared tables. + if ((flags & ~0x05) !== 0) r.malformed("malformed limits flags"); + if (flags & 0x04) this.feature("memory64"); + } + const wide = (flags & 0x04) !== 0; + const min = wide ? (r.u64(), 0) : r.u32(); + let max: number | null = null; + if (flags & 0x01) max = wide ? (r.u64(), 0) : r.u32(); + if (kind === "memory" && flags & 0x02 && max === null && !wide) { + this.invalid("shared memory must have maximum", r.pos); + } + return { min, max }; + } + + memoryType(r: Reader): void { + const offset = r.pos; + const { min, max } = this.limits(r, "memory"); + if (this.memory64) return; + if (min > MAX_MEMORY_PAGES) + this.invalid("memory size must be at most 65536 pages (4GiB)", offset); + if (max !== null && max > MAX_MEMORY_PAGES) { + this.invalid("memory size must be at most 65536 pages (4GiB)", offset); + } + if (max !== null && min > max) + this.invalid("size minimum must not be greater than maximum", offset); + } + + addMemory(): void { + this.memories++; + if (this.memories > 1) this.feature("multi-memory"); + } + + tableType(r: Reader): Table { + const offset = r.pos; + if (r.peek() === 0x40) { + // A table with an explicit initializer (function references). + r.u8(); + if (r.u8() !== 0x00) r.malformed("malformed table type"); + this.feature("function-references"); + } + const type = this.refType(r); + const { min, max } = this.limits(r, "table"); + if (min > MAX_TABLE_INIT) this.invalid("table size must be at most 10000000", offset); + if (max !== null && min > max) + this.invalid("size minimum must not be greater than maximum", offset); + return { type }; + } + + addTable(table: Table): void { + this.tables.push(table); + if (this.tables.length > 1) this.feature("reference-types"); + if (table.type === EXTERNREF) this.feature("reference-types"); + } + + globalType(r: Reader): Global { + const type = this.valType(r); + if (type === FUNCREF || type === EXTERNREF) this.feature("reference-types"); + const mut = r.u8(); + if (mut > 1) r.malformed("malformed mutability"); + return { type, mutable: mut === 1, imported: false }; + } + + // ── sections ───────────────────────────────────────────────────────── + + importSection(r: Reader): void { + const count = r.u32(); + if (count > MAX_IMPORTS) r.malformed("too many imports"); + for (let i = 0; i < count; i++) { + const module = r.name(); + r.name(); + if (!this.importModules.includes(module)) this.importModules.push(module); + this.imports++; + const offset = r.pos; + const kind = r.u8(); + switch (kind) { + case 0x00: { + const index = r.u32(); + this.funcTypeAt(index, offset); + this.funcs.push(index); + this.importedFuncs++; + break; + } + case 0x01: + this.addTable(this.tableType(r)); + break; + case 0x02: + this.memoryType(r); + this.addMemory(); + break; + case 0x03: { + const global = this.globalType(r); + if (global.mutable) this.feature("mutable-globals"); + global.imported = true; + this.globals.push(global); + this.importedGlobals++; + break; + } + case 0x04: + this.tagType(r); + break; + default: + r.malformed("malformed import kind"); + } + } + } + + tagType(r: Reader): void { + const offset = r.pos; + this.feature("exceptions"); + if (r.u8() !== 0x00) r.malformed("malformed tag attribute"); + const type = this.funcTypeAt(r.u32(), offset); + if (type.results.length !== 0 && !this.gcTypes) + this.invalid("non-empty tag result type", offset); + this.tags++; + } + + functionSection(r: Reader): void { + const count = r.u32(); + if (count > MAX_FUNCTIONS) r.malformed("too many functions"); + for (let i = 0; i < count; i++) { + const offset = r.pos; + const index = r.u32(); + this.funcTypeAt(index, offset); + this.funcs.push(index); + } + } + + tableSection(r: Reader): void { + const count = r.u32(); + for (let i = 0; i < count; i++) { + const hasInit = r.peek() === 0x40; + const table = this.tableType(r); + if (hasInit) this.constExpr(r, table.type); + this.addTable(table); + } + } + + memorySection(r: Reader): void { + const count = r.u32(); + for (let i = 0; i < count; i++) { + this.memoryType(r); + this.addMemory(); + } + } + + tagSection(r: Reader): void { + const count = r.u32(); + for (let i = 0; i < count; i++) this.tagType(r); + } + + globalSection(r: Reader): void { + const count = r.u32(); + if (count + this.globals.length > MAX_GLOBALS) r.malformed("too many globals"); + for (let i = 0; i < count; i++) { + const global = this.globalType(r); + this.constExpr(r, global.type); + this.globals.push(global); + } + } + + exportSection(r: Reader): void { + const count = r.u32(); + if (count > MAX_EXPORTS) r.malformed("too many exports"); + for (let i = 0; i < count; i++) { + const offset = r.pos; + const name = r.name(); + if (this.exportNames.has(name)) this.invalid("duplicate export name", offset); + this.exportNames.add(name); + this.exports++; + const kind = r.u8(); + const indexOffset = r.pos; + const index = r.u32(); + switch (kind) { + case 0x00: + if (index >= this.funcs.length) this.invalid(`unknown function ${index}`, indexOffset); + this.declaredRefs.add(index); + break; + case 0x01: + if (index >= this.tables.length) this.invalid(`unknown table ${index}`, indexOffset); + break; + case 0x02: + if (index >= this.memories) this.invalid(`unknown memory ${index}`, indexOffset); + break; + case 0x03: + if (index >= this.globals.length) this.invalid(`unknown global ${index}`, indexOffset); + if (this.globals[index]!.mutable) this.feature("mutable-globals"); + break; + case 0x04: + if (index >= this.tags) this.invalid(`unknown tag ${index}`, indexOffset); + break; + default: + r.malformed("malformed export kind"); + } + } + } + + startSection(r: Reader): void { + const offset = r.pos; + const index = r.u32(); + if (index >= this.funcs.length) this.invalid(`unknown function ${index}`, offset); + const type = this.funcTypeAt(this.funcs[index]!, offset); + if (type.params.length !== 0 || type.results.length !== 0) { + this.invalid("start function must have type [] -> []", offset); + } + } + + elementSection(r: Reader): void { + const count = r.u32(); + if (count > MAX_TABLE_INIT) r.malformed("too many element segments"); + for (let i = 0; i < count; i++) { + const offset = r.pos; + const flags = r.u32(); + if (flags > 7) r.malformed("malformed elements segment kind"); + const passiveOrDeclarative = (flags & 0x01) !== 0; + const explicitTable = (flags & 0x02) !== 0; + const usesExprs = (flags & 0x04) !== 0; + if (flags !== 0) this.feature(usesExprs || explicitTable ? "reference-types" : "bulk-memory"); + let tableIndex = 0; + if (!passiveOrDeclarative) { + if (explicitTable) tableIndex = r.u32(); + if (tableIndex >= this.tables.length) this.invalid(`unknown table ${tableIndex}`, offset); + this.constExpr(r, this.memory64 ? UNKNOWN : I32); + } + let elemType = FUNCREF; + if (passiveOrDeclarative || explicitTable) { + if (usesExprs) { + elemType = this.refType(r); + } else if (r.u8() !== 0x00) { + r.malformed("malformed element kind"); + } + } + if (!passiveOrDeclarative) { + const tableType = this.tables[tableIndex]!.type; + if (elemType !== UNKNOWN && tableType !== UNKNOWN && elemType !== tableType) { + this.invalid("type mismatch", offset); + } + } + const n = r.u32(); + for (let k = 0; k < n; k++) { + if (usesExprs) { + this.constExpr(r, elemType); + } else { + const at = r.pos; + const index = r.u32(); + if (index >= this.funcs.length) this.invalid(`unknown function ${index}`, at); + this.declaredRefs.add(index); + } + } + this.elemTypes.push(elemType); + } + } + + dataCountSection(r: Reader): void { + this.dataCount = r.u32(); + this.feature("bulk-memory"); + } + + dataSection(r: Reader): void { + const count = r.u32(); + if (count > MAX_DATA_SEGMENTS) r.malformed("too many data segments"); + if (this.dataCount !== null && count !== this.dataCount) { + r.malformed("data count and data section have inconsistent lengths"); + } + for (let i = 0; i < count; i++) { + const offset = r.pos; + const flags = r.u32(); + if (flags > 2) r.malformed("malformed data segment kind"); + if (flags !== 1) { + let memory = 0; + if (flags === 2) memory = r.u32(); + if (memory !== 0) this.feature("multi-memory"); + if (memory >= this.memories) this.invalid(`unknown memory ${memory}`, offset); + this.constExpr(r, this.memory64 ? UNKNOWN : I32); + } else { + this.feature("bulk-memory"); + } + const length = r.u32(); + r.skip(length); + } + this.dataSegments = count; + } + + customSection(r: Reader): void { + this.customSections.push(r.name()); + r.pos = r.end; + } + + // ── constant expressions ──────────────────────────────────────────── + + /** A constant expression producing `expected` (UNKNOWN: not checked). + * global.get may read any earlier immutable global, as in 3.0 and V8 + * (2.0 allowed only imported ones in global initializers). */ + constExpr(r: Reader, expected: number): void { + const stack: number[] = []; + const start = r.pos; + for (;;) { + const offset = r.pos; + const op = r.u8(); + switch (op) { + case 0x0b: { + if (expected !== UNKNOWN) { + if (stack.length !== 1 || !matches(stack[0]!, expected)) { + this.invalid("type mismatch", start); + } + } + return; + } + case 0x41: + r.sleb(32); + stack.push(I32); + break; + case 0x42: + r.sleb(64); + stack.push(I64); + break; + case 0x43: + r.skip(4); + stack.push(F32); + break; + case 0x44: + r.skip(8); + stack.push(F64); + break; + case 0x23: { + // A global initializer sees the globals before it (they are all + // that exist yet); offsets see every global. + const index = r.u32(); + if (index >= this.globals.length) this.invalid(`unknown global ${index}`, offset); + const global = this.globals[index]!; + if (global.mutable) this.invalid("constant expression required", offset); + stack.push(global.type); + break; + } + case 0xd0: { + this.feature("reference-types"); + const t = this.refHeapNull(r); + stack.push(t); + break; + } + case 0xd2: { + this.feature("reference-types"); + const index = r.u32(); + if (index >= this.funcs.length) this.invalid(`unknown function ${index}`, offset); + this.declaredRefs.add(index); + stack.push(FUNCREF); + break; + } + case 0x6a: + case 0x6b: + case 0x6c: + case 0x7c: + case 0x7d: + case 0x7e: { + this.feature("extended-const"); + const t = op <= 0x6c ? I32 : I64; + const b = stack.pop(); + const a = stack.pop(); + if (a === undefined || b === undefined || !matches(a, t) || !matches(b, t)) { + this.invalid("type mismatch", offset); + } + stack.push(t); + break; + } + case 0xfd: { + this.feature("simd"); + const sub = r.u32(); + if (sub !== 12) this.invalid("constant expression required", offset); + r.skip(16); + stack.push(V128); + break; + } + case 0xfb: + // GC's struct.new and friends: not typed here. + this.feature("gc"); + return this.skipToEnd(r); + default: + this.invalid("constant expression required", offset); + } + } + } + + skipToEnd(r: Reader): void { + // Only reachable for GC constant expressions, whose immediates this + // decoder does not model: skip to the next end byte. + while (r.u8() !== 0x0b) { + /* scan */ + } + } + + /** ref.null's heap type, as a value type (UNKNOWN for GC heap types). */ + refHeapNull(r: Reader): number { + const b = r.peek(); + if (b === FUNCREF || b === EXTERNREF) { + r.u8(); + return b; + } + this.heapType(r); + return UNKNOWN; + } + + // ── code ───────────────────────────────────────────────────────────── + + codeSection(r: Reader): void { + const count = r.u32(); + this.sawCode = true; + const defined = this.funcs.length - this.importedFuncs; + if (count !== defined) r.malformed("function and code section have inconsistent lengths"); + this.codeCount = count; + for (let i = 0; i < count; i++) { + const size = r.u32(); + if (size > r.end - r.pos) r.malformed("length out of bounds"); + if (size > MAX_FUNCTION_SIZE) r.malformed("function body too large"); + const body = new Reader(this.bytes, r.pos, r.pos + size); + const type = this.funcTypeAt(this.funcs[this.importedFuncs + i]!, r.pos); + new FunctionValidator(this, body, type).run(); + r.pos += size; + } + } + + decode(): void { + const bytes = this.bytes; + const r = new Reader(bytes, 0, bytes.length); + if ( + bytes.length < 4 || + bytes[0] !== 0 || + bytes[1] !== 0x61 || + bytes[2] !== 0x73 || + bytes[3] !== 0x6d + ) { + r.malformed("magic header not detected"); + } + r.pos = 4; + if (bytes.length < 8) r.malformed("unexpected end"); + if (bytes[4] !== 1 || bytes[5] !== 0 || bytes[6] !== 0 || bytes[7] !== 0) { + r.malformed("unknown binary version"); + } + r.pos = 8; + let last = 0; + while (!r.eof()) { + const id = r.u8(); + const size = r.u32(); + if (size > r.end - r.pos) r.malformed("section size out of bounds"); + const section = new Reader(bytes, r.pos, r.pos + size); + if (id !== 0) { + const order = SECTION_ORDER[id]; + if (order === undefined) return r.malformed("malformed section id"); + if (order <= last) r.malformed("unexpected section (out of order or duplicate)"); + last = order; + } + if (!(this.gcTypes && id !== 0)) { + switch (id) { + case 0: + this.customSection(section); + break; + case 1: + this.typeSection(section); + break; + case 2: + this.importSection(section); + break; + case 3: + this.functionSection(section); + break; + case 4: + this.tableSection(section); + break; + case 5: + this.memorySection(section); + break; + case 6: + this.globalSection(section); + break; + case 7: + this.exportSection(section); + break; + case 8: + this.startSection(section); + break; + case 9: + this.elementSection(section); + break; + case 10: + this.codeSection(section); + break; + case 11: + this.dataSection(section); + break; + case 12: + this.dataCountSection(section); + break; + case 13: + this.tagSection(section); + break; + } + } else { + section.pos = section.end; + } + if (section.pos !== section.end) { + r.pos = section.pos; + r.malformed("section size mismatch"); + } + r.pos = section.end; + } + if (!this.gcTypes) { + if (!this.sawCode && this.funcs.length > this.importedFuncs) { + r.malformed("function and code section have inconsistent lengths"); + } + if (this.dataCount !== null && this.dataSegments !== this.dataCount && this.dataCount !== 0) { + r.malformed("data count and data section have inconsistent lengths"); + } + } + } +} + +function isRef(t: number): boolean { + return t === FUNCREF || t === EXTERNREF; +} + +function isNum(t: number): boolean { + return t === I32 || t === I64 || t === F32 || t === F64 || t === V128; +} + +function matches(actual: number, expected: number): boolean { + return actual === expected || actual === UNKNOWN || expected === UNKNOWN; +} + +/** Validates one function body (the algorithm of the specification's + * validation appendix). Once an instruction needs a proposal outside the + * translatable set, the rest of the body is only decoded. */ +class FunctionValidator { + m: ModuleDecoder; + r: Reader; + type: FuncType; + locals: number[] = []; + vals: number[] = []; + ctrls: Frame[] = []; + typed = true; + + constructor(m: ModuleDecoder, r: Reader, type: FuncType) { + this.m = m; + this.r = r; + this.type = type; + } + + invalid(message: string): never { + throw new WasmError("invalid", message, this.r.pos); + } + + /** Stops typing: the rest of the body only decodes. */ + untyped(feature: WasmFeature): void { + this.m.feature(feature); + this.typed = false; + } + + run(): void { + const r = this.r; + for (const p of this.type.params) this.locals.push(p); + const groups = r.u32(); + let total = this.locals.length; + for (let i = 0; i < groups; i++) { + const n = r.u32(); + total += n; + if (total > MAX_LOCALS) r.malformed("too many locals"); + const t = this.m.valType(r); + if (t === V128) this.typed = false; + if (isRef(t)) this.m.feature("reference-types"); + for (let k = 0; k < n; k++) this.locals.push(t); + } + if (this.type.params.includes(V128) || this.type.results.includes(V128)) this.typed = false; + if (this.m.memory64 || this.m.gcTypes) this.typed = false; + this.pushCtrl(-1, [], this.type.results); + while (this.ctrls.length > 0) { + if (r.eof()) r.malformed("unexpected end of section or function"); + this.instruction(); + } + if (!r.eof()) r.malformed("operators remaining after end of function"); + } + + // The operand stack. + push(t: number): void { + this.vals.push(t); + } + + pop(): number { + const frame = this.ctrls[this.ctrls.length - 1]!; + if (this.vals.length === frame.height) { + if (frame.unreachable) return UNKNOWN; + this.invalid("type mismatch"); + } + return this.vals.pop()!; + } + + popExpect(expected: number): number { + const actual = this.pop(); + if (!matches(actual, expected)) this.invalid("type mismatch"); + // Unreachable code's unknown operand stays unknown when pushed back + // (br_if, br_table), so later labels can still meet it. + return actual; + } + + pushAll(types: number[]): void { + for (const t of types) this.push(t); + } + + popAll(types: number[]): number[] { + const out: number[] = []; + for (let i = types.length - 1; i >= 0; i--) out.unshift(this.popExpect(types[i]!)); + return out; + } + + pushCtrl(opcode: number, start: number[], end: number[]): void { + this.ctrls.push({ + opcode, + start, + end, + height: this.vals.length, + unreachable: false, + hasElse: false, + }); + this.pushAll(start); + } + + popCtrl(): Frame { + if (this.ctrls.length === 0) this.invalid("control stack underflow"); + const frame = this.ctrls[this.ctrls.length - 1]!; + this.popAll(frame.end); + if (this.vals.length !== frame.height) this.invalid("type mismatch"); + this.ctrls.pop(); + return frame; + } + + labelTypes(frame: Frame): number[] { + return frame.opcode === 0x03 ? frame.start : frame.end; + } + + setUnreachable(): void { + const frame = this.ctrls[this.ctrls.length - 1]!; + this.vals.length = frame.height; + frame.unreachable = true; + } + + label(depth: number): Frame { + if (depth >= this.ctrls.length) this.invalid(`unknown label ${depth}`); + return this.ctrls[this.ctrls.length - 1 - depth]!; + } + + blockType(): FuncType { + const r = this.r; + const b = r.peek(); + if (b === 0x40) { + r.u8(); + return { params: [], results: [] }; + } + if ( + b === I32 || + b === I64 || + b === F32 || + b === F64 || + b === V128 || + b === FUNCREF || + b === EXTERNREF + ) { + return { params: [], results: [this.m.valType(r)] }; + } + if (b >= 0x60) { + return { params: [], results: [this.m.valType(r)] }; + } + const offset = r.pos; + const index = r.sleb(33); + if (index < 0) r.malformed("malformed block type"); + this.m.feature("multi-value"); + return this.m.funcTypeAt(index, offset); + } + + local(index: number): number { + if (index >= this.locals.length) this.invalid(`unknown local ${index}`); + return this.locals[index]!; + } + + global(index: number): Global { + if (index >= this.m.globals.length) this.invalid(`unknown global ${index}`); + return this.m.globals[index]!; + } + + table(index: number): Table { + if (index >= this.m.tables.length) this.invalid(`unknown table ${index}`); + return this.m.tables[index]!; + } + + /** A memory other than 0 (multi-memory): it must exist; typing stops. */ + otherMemory(index: number): void { + if (index >= this.m.memories) this.invalid(`unknown memory ${index}`); + this.untyped("multi-memory"); + } + + memoryZero(): void { + if (this.m.memories === 0) this.invalid("unknown memory 0"); + } + + func(index: number): FuncType { + if (index >= this.m.funcs.length) this.invalid(`unknown function ${index}`); + return this.m.funcTypeAt(this.m.funcs[index]!, this.r.pos); + } + + /** 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); + } + if (this.m.memory64) r.u64(); + else r.u32(); + if (!this.typed) return; + if (memory >= this.m.memories) this.invalid(`unknown memory ${memory}`); + if (align > naturalAlign) this.invalid("alignment must not be larger than natural"); + } + + instruction(): void { + const r = this.r; + const op = r.u8(); + if (!this.typed) { + this.scan(op); + return; + } + switch (op) { + case 0x00: + this.setUnreachable(); + return; + case 0x01: + return; + case 0x02: + case 0x03: { + const bt = this.blockType(); + this.popAll(bt.params); + this.pushCtrl(op, bt.params, bt.results); + return; + } + case 0x04: { + const bt = this.blockType(); + this.popExpect(I32); + this.popAll(bt.params); + this.pushCtrl(op, bt.params, bt.results); + return; + } + case 0x05: { + const frame = this.ctrls[this.ctrls.length - 1]!; + if (frame.opcode !== 0x04 || frame.hasElse) this.invalid("else without matching if"); + this.popCtrl(); + this.pushCtrl(0x04, frame.start, frame.end); + this.ctrls[this.ctrls.length - 1]!.hasElse = true; + return; + } + case 0x0b: { + const frame = this.popCtrl(); + // An if without else must leave its params as its results. + if (frame.opcode === 0x04 && !frame.hasElse) { + if (frame.start.length !== frame.end.length) this.invalid("type mismatch"); + for (let i = 0; i < frame.start.length; i++) { + if (frame.start[i] !== frame.end[i]) this.invalid("type mismatch"); + } + } + this.pushAll(frame.end); + return; + } + case 0x0c: { + const depth = r.u32(); + this.popAll(this.labelTypes(this.label(depth))); + this.setUnreachable(); + return; + } + case 0x0d: { + const depth = r.u32(); + this.popExpect(I32); + // br_if leaves the label's types (even over unknown operands); + // br_table below leaves what it popped. + const types = this.labelTypes(this.label(depth)); + this.popAll(types); + this.pushAll(types); + return; + } + case 0x0e: { + const n = r.u32(); + const targets: number[] = []; + for (let i = 0; i < n; i++) targets.push(r.u32()); + const defaultDepth = r.u32(); + this.popExpect(I32); + const arity = this.labelTypes(this.label(defaultDepth)).length; + for (const depth of targets) { + const types = this.labelTypes(this.label(depth)); + if (types.length !== arity) this.invalid("type mismatch"); + this.pushAll(this.popAll(types)); + } + this.popAll(this.labelTypes(this.label(defaultDepth))); + this.setUnreachable(); + return; + } + case 0x0f: + this.popAll(this.type.results); + this.setUnreachable(); + return; + case 0x10: { + const type = this.func(r.u32()); + this.popAll(type.params); + this.pushAll(type.results); + return; + } + case 0x11: { + const typeIndex = r.u32(); + const tableIndex = r.u32(); + if (tableIndex !== 0) this.m.feature("reference-types"); + const table = this.table(tableIndex); + if (table.type !== FUNCREF && table.type !== UNKNOWN) this.invalid("type mismatch"); + const type = this.m.funcTypeAt(typeIndex, r.pos); + this.popExpect(I32); + this.popAll(type.params); + this.pushAll(type.results); + return; + } + case 0x12: + case 0x13: + case 0x14: + case 0x15: + this.untyped(op === 0x14 || op === 0x15 ? "function-references" : "tail-call"); + this.scan(op); + return; + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x18: + case 0x19: + case 0x1f: + this.untyped("exceptions"); + this.scan(op); + return; + case 0x1a: + this.pop(); + return; + case 0x1b: { + this.popExpect(I32); + const t1 = this.pop(); + const t2 = this.pop(); + if (!(isNum(t1) || t1 === UNKNOWN) || !(isNum(t2) || t2 === UNKNOWN)) + this.invalid("type mismatch"); + if (t1 !== t2 && t1 !== UNKNOWN && t2 !== UNKNOWN) this.invalid("type mismatch"); + this.push(t1 === UNKNOWN ? t2 : t1); + return; + } + case 0x1c: { + const n = r.u32(); + if (n !== 1) this.invalid("invalid result arity"); + const t = this.m.valType(r); + if (isRef(t)) this.m.feature("reference-types"); + this.popExpect(I32); + this.popExpect(t); + this.popExpect(t); + this.push(t); + return; + } + case 0x20: + this.push(this.local(r.u32())); + return; + case 0x21: + this.popExpect(this.local(r.u32())); + return; + case 0x22: { + const t = this.local(r.u32()); + this.popExpect(t); + this.push(t); + return; + } + case 0x23: + this.push(this.global(r.u32()).type); + return; + case 0x24: { + const global = this.global(r.u32()); + if (!global.mutable) this.invalid("global is immutable"); + this.popExpect(global.type); + return; + } + case 0x25: { + this.m.feature("reference-types"); + const table = this.table(r.u32()); + this.popExpect(I32); + this.push(table.type); + return; + } + case 0x26: { + this.m.feature("reference-types"); + const table = this.table(r.u32()); + this.popExpect(table.type); + this.popExpect(I32); + return; + } + case 0x3f: + case 0x40: { + const memory = r.u8(); + if (memory !== 0) { + // A memory index (multi-memory) or a malformed reserved byte. + r.pos--; + const index = r.u32(); + if (index !== 0) this.otherMemory(index); + if (!this.typed) return; + } + this.memoryZero(); + if (op === 0x40) this.popExpect(I32); + this.push(I32); + return; + } + case 0x41: + r.sleb(32); + this.push(I32); + return; + case 0x42: + r.sleb(64); + this.push(I64); + return; + case 0x43: + r.skip(4); + this.push(F32); + return; + case 0x44: + r.skip(8); + this.push(F64); + return; + case 0xd0: { + this.m.feature("reference-types"); + const t = this.m.refHeapNull(r); + if (t === UNKNOWN) { + this.typed = false; + return; + } + this.push(t); + return; + } + case 0xd1: { + this.m.feature("reference-types"); + const t = this.pop(); + if (!isRef(t) && t !== UNKNOWN) this.invalid("type mismatch"); + this.push(I32); + return; + } + case 0xd2: { + this.m.feature("reference-types"); + const index = r.u32(); + this.func(index); + if (!this.m.declaredRefs.has(index)) this.invalid("undeclared function reference"); + this.push(FUNCREF); + return; + } + case 0xd3: + case 0xd4: + case 0xd5: + case 0xd6: + this.untyped(op === 0xd3 ? "gc" : "function-references"); + this.scan(op); + return; + case 0xfc: + this.prefixFC(); + return; + case 0xfd: + this.untyped("simd"); + this.scanSimd(); + return; + case 0xfe: + this.untyped("threads"); + this.scanAtomic(); + return; + case 0xfb: + this.untyped("gc"); + r.pos = r.end; + this.ctrls.length = 0; + return; + } + if (op >= 0x28 && op <= 0x3e) { + const k = op - 0x28; + this.memarg(MEMORY_ALIGN[k]!); + if (!this.typed) return; + this.memoryZero(); + if (op <= 0x35) { + this.popExpect(I32); + this.push(MEMORY_TYPE[k]!); + } else { + this.popExpect(MEMORY_TYPE[k]!); + this.popExpect(I32); + } + return; + } + if (op >= 0x45 && op <= 0xc4) { + if (op >= 0xc0) this.m.feature("sign-extension"); + const [operand, count, result] = numericSignature(op); + for (let i = 0; i < count; i++) this.popExpect(operand); + this.push(result); + return; + } + r.pos--; + r.malformed(`illegal opcode 0x${op.toString(16)}`); + } + + prefixFC(): void { + const r = this.r; + const sub = r.u32(); + if (sub <= 7) { + this.m.feature("nontrapping-fptoint"); + const from = sub <= 1 || (sub >= 4 && sub <= 5) ? F32 : F64; + const to = sub <= 3 ? I32 : I64; + this.popExpect(from); + this.push(to); + return; + } + switch (sub) { + case 8: { + this.m.feature("bulk-memory"); + const segment = r.u32(); + const memory = r.u32(); + if (memory !== 0) { + this.otherMemory(memory); + return; + } + this.memoryZero(); + const dataCount = this.m.dataCount; + if (dataCount === null) return r.malformed("data count section required"); + if (segment >= dataCount) this.invalid(`unknown data segment ${segment}`); + this.popExpect(I32); + this.popExpect(I32); + this.popExpect(I32); + return; + } + case 9: { + this.m.feature("bulk-memory"); + const segment = r.u32(); + const dataCount = this.m.dataCount; + if (dataCount === null) return r.malformed("data count section required"); + if (segment >= dataCount) this.invalid(`unknown data segment ${segment}`); + return; + } + case 10: { + this.m.feature("bulk-memory"); + const dst = r.u32(); + const src = r.u32(); + if (dst !== 0 || src !== 0) { + this.otherMemory(Math.max(dst, src)); + return; + } + this.memoryZero(); + this.popExpect(I32); + this.popExpect(I32); + this.popExpect(I32); + return; + } + case 11: { + this.m.feature("bulk-memory"); + const memory = r.u32(); + if (memory !== 0) { + this.otherMemory(memory); + return; + } + this.memoryZero(); + this.popExpect(I32); + this.popExpect(I32); + this.popExpect(I32); + return; + } + case 12: { + this.m.feature("bulk-memory"); + const segment = r.u32(); + const table = this.table(r.u32()); + if (segment >= this.m.elemTypes.length) this.invalid(`unknown elem segment ${segment}`); + const elemType = this.m.elemTypes[segment]!; + if (!matches(elemType, table.type)) this.invalid("type mismatch"); + this.popExpect(I32); + this.popExpect(I32); + this.popExpect(I32); + return; + } + case 13: { + this.m.feature("bulk-memory"); + const segment = r.u32(); + if (segment >= this.m.elemTypes.length) this.invalid(`unknown elem segment ${segment}`); + return; + } + case 14: { + this.m.feature("bulk-memory"); + const dst = this.table(r.u32()); + const src = this.table(r.u32()); + if (!matches(src.type, dst.type)) this.invalid("type mismatch"); + this.popExpect(I32); + this.popExpect(I32); + this.popExpect(I32); + return; + } + case 15: { + this.m.feature("reference-types"); + const table = this.table(r.u32()); + this.popExpect(I32); + this.popExpect(table.type); + this.push(I32); + return; + } + case 16: + this.m.feature("reference-types"); + this.table(r.u32()); + this.push(I32); + return; + case 17: { + this.m.feature("reference-types"); + const table = this.table(r.u32()); + this.popExpect(I32); + this.popExpect(table.type); + this.popExpect(I32); + return; + } + } + r.malformed(`illegal opcode 0xfc ${sub}`); + } + + // ── decode-only scanning (proposals outside the translatable set) ───── + + /** One instruction, decoded for its immediates only. Tracks block + * nesting so that the body's final end is found. */ + scan(op: number): void { + const r = this.r; + switch (op) { + case 0x02: + case 0x03: + case 0x04: + case 0x06: + this.scanBlockType(); + this.ctrls.push({ + opcode: op, + start: [], + end: [], + height: 0, + unreachable: true, + hasElse: false, + }); + return; + case 0x1f: { + this.m.feature("exceptions"); + this.scanBlockType(); + const n = r.u32(); + for (let i = 0; i < n; i++) { + const kind = r.u8(); + if (kind > 3) r.malformed("malformed catch kind"); + if (kind < 2) r.u32(); + r.u32(); + } + this.ctrls.push({ + opcode: op, + start: [], + end: [], + height: 0, + unreachable: true, + hasElse: false, + }); + return; + } + case 0x0b: + this.ctrls.pop(); + return; + case 0x18: + // delegate ends its try block. + this.m.feature("exceptions"); + r.u32(); + this.ctrls.pop(); + return; + case 0xfc: + this.scanFC(); + return; + case 0xfd: + this.m.feature("simd"); + this.scanSimd(); + return; + case 0xfe: + this.m.feature("threads"); + this.scanAtomic(); + return; + case 0xfb: + this.m.feature("gc"); + r.pos = r.end; + this.ctrls.length = 0; + return; + } + this.scanImmediates(op); + } + + scanBlockType(): void { + const r = this.r; + const b = r.peek(); + if (b === 0x40) { + r.u8(); + return; + } + if (b >= 0x60) { + this.m.valType(r); + return; + } + if (r.sleb(33) < 0) r.malformed("malformed block type"); + } + + /** The immediates of a single-byte opcode that opens no block. */ + scanImmediates(op: number): void { + const r = this.r; + switch (op) { + case 0x00: + case 0x01: + case 0x05: + case 0x0f: + case 0x1a: + case 0x1b: + case 0x0a: + case 0x19: + case 0xd1: + case 0xd3: + case 0xd4: + if (op === 0x0a || op === 0x19) this.m.feature("exceptions"); + return; + case 0x07: + case 0x08: + case 0x09: + case 0x0c: + case 0x0d: + case 0x10: + case 0x12: + case 0x14: + case 0x15: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0xd2: + case 0xd5: + case 0xd6: + if (op === 0x07 || op === 0x08 || op === 0x09) this.m.feature("exceptions"); + if (op === 0x12) this.m.feature("tail-call"); + if (op === 0x14 || op === 0x15 || op === 0xd5 || op === 0xd6) + this.m.feature("function-references"); + r.u32(); + return; + case 0x0e: { + const n = r.u32(); + for (let i = 0; i <= n; i++) r.u32(); + return; + } + case 0x11: + case 0x13: + if (op === 0x13) this.m.feature("tail-call"); + r.u32(); + r.u32(); + return; + case 0x1c: { + const n = r.u32(); + for (let i = 0; i < n; i++) this.m.valType(r); + return; + } + case 0x3f: + case 0x40: + r.u32(); + return; + case 0x41: + r.sleb(32); + return; + case 0x42: + r.sleb(64); + return; + case 0x43: + r.skip(4); + return; + case 0x44: + r.skip(8); + return; + case 0xd0: + this.m.refHeapNull(r); + return; + } + if (op >= 0x28 && op <= 0x3e) { + this.scanMemarg(); + return; + } + if (op >= 0x45 && op <= 0xc4) { + if (op >= 0xc0) this.m.feature("sign-extension"); + return; + } + r.pos--; + r.malformed(`illegal opcode 0x${op.toString(16)}`); + } + + 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"); + if (this.m.memory64) r.u64(); + else r.u32(); + } + + scanFC(): void { + const r = this.r; + const sub = r.u32(); + if (sub <= 7) { + this.m.feature("nontrapping-fptoint"); + return; + } + if (sub > 17) r.malformed(`illegal opcode 0xfc ${sub}`); + this.m.feature(sub >= 15 ? "reference-types" : "bulk-memory"); + switch (sub) { + case 8: + case 10: + case 12: + case 14: + r.u32(); + r.u32(); + return; + default: + r.u32(); + } + } + + scanSimd(): void { + const r = this.r; + const sub = r.u32(); + if (sub <= 11 || sub === 92 || sub === 93) { + this.scanMemarg(); + return; + } + if (sub === 12 || sub === 13) { + r.skip(16); + return; + } + if (sub >= 21 && sub <= 34) { + r.u8(); + return; + } + if (sub >= 84 && sub <= 91) { + this.scanMemarg(); + r.u8(); + return; + } + if (sub > 0x113) r.malformed(`illegal opcode 0xfd ${sub}`); + } + + scanAtomic(): void { + const r = this.r; + const sub = r.u32(); + if (sub === 0x03) { + if (r.u8() !== 0) r.malformed("zero byte expected"); + return; + } + if (sub > 0x4e || (sub > 0x03 && sub < 0x10)) r.malformed(`illegal opcode 0xfe ${sub}`); + this.scanMemarg(); + } +} + +/** Decodes and validates `bytes` (a whole module). Never throws. */ +export function analyzeWasm(bytes: Uint8Array): WasmModuleSummary { + const m = new ModuleDecoder(bytes); + let status: WasmModuleSummary["status"] = "valid"; + let error: string | null = null; + try { + m.decode(); + } catch (e) { + if (!(e instanceof WasmError)) throw e; + status = e.kind; + error = `${e.message} (at byte ${e.offset})`; + } + const features: string[] = []; + const unsupported: string[] = []; + for (const feature of WASM_FEATURES) { + if (!m.features.has(feature)) continue; + features.push(feature); + if (!TRANSLATABLE.has(feature)) unsupported.push(feature); + } + if (status === "valid" && unsupported.length > 0) status = "unsupported"; + return { + size: bytes.length, + status, + error, + features, + unsupported, + functions: Math.max(0, m.funcs.length - m.importedFuncs), + imports: m.imports, + exports: m.exports, + importModules: m.importModules, + memories: m.memories, + tables: m.tables.length, + globals: m.globals.length, + customSections: m.customSections, + }; +} + +/** True when `bytes` starts with the WebAssembly magic and version 1. */ +export function isWasmHeader(bytes: Uint8Array): boolean { + return ( + bytes.length >= 8 && + bytes[0] === 0 && + bytes[1] === 0x61 && + bytes[2] === 0x73 && + bytes[3] === 0x6d && + bytes[4] === 1 && + bytes[5] === 0 && + bytes[6] === 0 && + bytes[7] === 0 + ); +} diff --git a/packages/compiler/src/wasm/discover.test.ts b/packages/compiler/src/wasm/discover.test.ts new file mode 100644 index 000000000..1a3ba5e26 --- /dev/null +++ b/packages/compiler/src/wasm/discover.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from "vitest"; +import { decodeBase64, packageRootOf } from "./discover.js"; + +describe("decodeBase64", () => { + test("decodes standard and URL-safe spellings, with or without padding", () => { + const bytes = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 0xfb, 0xff]); + const standard = Buffer.from(bytes).toString("base64"); + expect(decodeBase64(standard)).toEqual(bytes); + expect(decodeBase64(standard.replace(/=+$/, ""))).toEqual(bytes); + expect(decodeBase64(Buffer.from(bytes).toString("base64url"))).toEqual(bytes); + }); + + test("answers null for text that is not base64", () => { + expect(decodeBase64("AGFzbQ*")).toBeNull(); + expect(decodeBase64("AGFzb")).toBeNull(); + expect(decodeBase64("AGFzbQ===")).toBeNull(); + }); +}); + +describe("packageRootOf", () => { + test("finds the innermost package, scoped or not", () => { + expect(packageRootOf("/p/node_modules/quickjs-wasi/dist/index.js")).toBe( + "/p/node_modules/quickjs-wasi", + ); + expect(packageRootOf("/p/node_modules/a/node_modules/@scope/b/lib/x.js")).toBe( + "/p/node_modules/a/node_modules/@scope/b", + ); + expect(packageRootOf("/p/src/main.ts")).toBeNull(); + }); +}); diff --git a/packages/compiler/src/wasm/discover.ts b/packages/compiler/src/wasm/discover.ts new file mode 100644 index 000000000..610a389f1 --- /dev/null +++ b/packages/compiler/src/wasm/discover.ts @@ -0,0 +1,426 @@ +/* Build-time discovery of the WebAssembly modules a program can load. + * + * `scriptc coverage` reports every module whose bytes the build can see, + * from four sources: + * + * 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"`); + * 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 + * program files mention `WebAssembly` (this finds paths computed at run + * time, such as a loader's default argument); + * 4. config: the `scriptc.wasm` list in the entry's package.json and the + * `--wasm ` flag. + * + * Each module is decoded and validated (binary.ts) and reported once, by + * content, with every source that found it. Nothing here changes what a + * build emits. */ +import { createHash } from "node:crypto"; +import { readFileSync } from "node:fs"; +import { dirname, isAbsolute, join, resolve } from "node:path"; +import * as ts from "../frontend/ts7/adapter.js"; +import { + trackedAccessibleEntries, + trackedFileExists, + trackedReadFile, +} from "../frontend/input-tracker.js"; +import { analyzeWasm, isWasmHeader, type WasmModuleSummary } from "./binary.js"; + +export type WasmSourceKind = "path" | "inline" | "package" | "config"; + +export interface WasmSource { + kind: WasmSourceKind; + /** The source file (path, inline) or the file that names the module. */ + file: string; + /** 1-based line of the site, when there is one. */ + line?: number; +} + +/** One discovered module. JSON-shaped: the coverage verdict cache stores it. */ +export interface WasmModuleReport extends WasmModuleSummary { + sha256: string; + /** The module file, absent for inline modules. */ + path?: string; + sources: WasmSource[]; +} + +export interface WasmDiscoveryInput { + /** The program's source files (entry and modules). */ + files: readonly ts.SourceFile[]; + /** The entry file. */ + entry: string; + /** --wasm files, absolute. */ + configured: readonly string[]; + /** require.resolve from `fromFile`, or null when it does not resolve. */ + resolveRequest: (fromFile: string, specifier: string) => string | null; +} + +const SCAN_KINDS: ReadonlySet = new Set([ + ts.SyntaxKind.StringLiteral, + ts.SyntaxKind.NoSubstitutionTemplateLiteral, + ts.SyntaxKind.CallExpression, + ts.SyntaxKind.NewExpression, + ts.SyntaxKind.BinaryExpression, + ts.SyntaxKind.ArrayLiteralExpression, +]); + +/** The base64 spelling of a module's first bytes ("\0asm"). */ +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; +/** Larger files are reported by size but not read. */ +const MAX_MODULE_BYTES = 256 * 1024 * 1024; + +class Discovery { + input: WasmDiscoveryInput; + byHash = new Map(); + byPath = new Map(); + order: string[] = []; + + constructor(input: WasmDiscoveryInput) { + this.input = input; + } + + /** Records a module and one source that found it; answers its hash. */ + addBytes(bytes: Uint8Array, path: string | undefined, source: WasmSource): string { + const sha256 = createHash("sha256").update(bytes).digest("hex"); + let report = this.byHash.get(sha256); + if (report === undefined) { + const summary = analyzeWasm(bytes); + report = { + size: summary.size, + status: summary.status, + error: summary.error, + features: summary.features, + unsupported: summary.unsupported, + functions: summary.functions, + imports: summary.imports, + exports: summary.exports, + importModules: summary.importModules, + memories: summary.memories, + tables: summary.tables, + globals: summary.globals, + customSections: summary.customSections, + sha256, + sources: [], + }; + this.byHash.set(sha256, report); + this.order.push(sha256); + } + if (report.path === undefined && path !== undefined) report.path = path; + addSource(report, source); + return sha256; + } + + addFile(path: string, source: WasmSource): void { + path = resolve(path); + const known = this.byPath.get(path); + if (known !== undefined) { + addSource(this.byHash.get(known)!, source); + return; + } + // The tracked read makes the coverage verdict cache depend on the file. + if (trackedReadFile(path) === null) return; + let bytes: Uint8Array; + try { + bytes = new Uint8Array(readFileSync(path)); + } catch { + return; + } + if (bytes.length > MAX_MODULE_BYTES) return; + this.byPath.set(path, this.addBytes(bytes, path, source)); + } + + // ── source 1 and 2: the program's source text ─────────────────────── + + scanFile(sf: ts.SourceFile): void { + const text = sf.text; + const paths = text.includes(".wasm"); + const base64 = text.includes(BASE64_MAGIC); + const bytes = BYTES_MAGIC.test(text); + if (!paths && !base64 && !bytes) return; + const file = sf.fileName; + const lineOf = (node: ts.Node): number => + ts.getLineAndCharacterOfPosition(sf, node.getStart()).line + 1; + ts.walkPreorder( + sf, + (node) => { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) { + if (base64 && node.text.startsWith(BASE64_MAGIC)) { + const decoded = decodeBase64(node.text); + if (decoded !== null && isWasmHeader(decoded)) { + this.addBytes(decoded, undefined, { kind: "inline", file, line: lineOf(node) }); + } + } + return undefined; + } + if (ts.isArrayLiteralExpression(node)) { + if (bytes) { + const literal = byteArrayLiteral(node); + if (literal !== null && isWasmHeader(literal)) { + this.addBytes(literal, undefined, { kind: "inline", file, line: lineOf(node) }); + return "skip"; + } + } + return undefined; + } + if (!paths) return undefined; + const path = this.resolvedPath(sf, node); + if (path !== null) this.addFile(path, { kind: "path", file, line: lineOf(node) }); + return undefined; + }, + SCAN_KINDS, + ); + } + + /** The module path a resolution expression names, when it ends in .wasm. */ + resolvedPath(sf: ts.SourceFile, node: ts.Node): string | null { + const dir = dirname(sf.fileName); + if (ts.isCallExpression(node)) { + const callee = node.expression; + const first = node.arguments[0]; + if (first === undefined) return null; + // require.resolve / createRequire(...).resolve / import.meta.resolve + if (ts.isPropertyAccessExpression(callee) && callee.name.text === "resolve") { + const specifier = wasmLiteral(first); + if (specifier === null) { + return this.joinedPath(dir, node.arguments); + } + const receiver = callee.expression; + if (ts.isMetaProperty(receiver) || isRequireLike(receiver)) { + if (specifier.startsWith("./") || specifier.startsWith("../")) { + return resolve(dir, specifier); + } + if (isAbsolute(specifier)) return specifier; + return this.input.resolveRequest(sf.fileName, specifier); + } + return null; + } + // path.join(__dirname, "x.wasm") and its resolve/bare spellings + if ( + (ts.isPropertyAccessExpression(callee) && + (callee.name.text === "join" || callee.name.text === "resolve")) || + (ts.isIdentifier(callee) && (callee.text === "join" || callee.text === "resolve")) + ) { + return this.joinedPath(dir, node.arguments); + } + return null; + } + if (ts.isNewExpression(node)) { + // new URL("./x.wasm", import.meta.url) + if (!ts.isIdentifier(node.expression) || node.expression.text !== "URL") return null; + const args = node.arguments; + if (args === undefined || args.length !== 2) return null; + const specifier = wasmLiteral(args[0]!); + const base = args[1]!; + if (specifier === null || !isImportMetaUrl(base)) return null; + if (specifier.startsWith("./") || specifier.startsWith("../")) return resolve(dir, specifier); + return null; + } + if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) { + // __dirname + "/x.wasm" + if (!isDirname(node.left)) return null; + const rest = wasmLiteral(node.right); + return rest === null ? null : join(dir, rest); + } + return null; + } + + joinedPath(dir: string, args: readonly ts.Expression[]): string | null { + if (args.length < 2 || !isDirname(args[0]!)) return null; + const parts: string[] = [dir]; + for (let i = 1; i < args.length; i++) { + const arg = args[i]!; + if (!ts.isStringLiteral(arg) && !ts.isNoSubstitutionTemplateLiteral(arg)) return null; + parts.push(arg.text); + } + if (!parts[parts.length - 1]!.endsWith(".wasm")) return null; + let path = parts[0]!; + for (let i = 1; i < parts.length; i++) path = resolve(path, parts[i]!); + return path; + } + + // ── source 3: package files ───────────────────────────────────────── + + scanPackages(): void { + const roots = new Map(); + for (const sf of this.input.files) { + const root = packageRootOf(sf.fileName); + if (root === null) continue; + const mentions = sf.text.includes("WebAssembly"); + roots.set(root, (roots.get(root) ?? false) || mentions); + } + for (const [root, mentions] of roots) { + if (!mentions) continue; + const found: string[] = []; + collectWasmFiles(root, 0, found); + found.sort(); + for (const path of found) + this.addFile(path, { kind: "package", file: join(root, "package.json") }); + } + } + + // ── source 4: configuration ───────────────────────────────────────── + + scanConfig(): void { + const manifest = nearestPackageJson(dirname(this.input.entry)); + if (manifest !== null) { + const text = trackedReadFile(manifest); + if (text !== null) { + let listed: unknown; + try { + const parsed = JSON.parse(text) as { scriptc?: { wasm?: unknown } }; + listed = parsed.scriptc?.wasm; + } catch { + listed = undefined; + } + if (Array.isArray(listed)) { + for (const entry of listed) { + if (typeof entry === "string") { + this.addFile(resolve(dirname(manifest), entry), { kind: "config", file: manifest }); + } + } + } + } + } + for (const path of this.input.configured) { + this.addFile(path, { kind: "config", file: path }); + } + } + + run(): WasmModuleReport[] { + for (const sf of this.input.files) this.scanFile(sf); + this.scanPackages(); + this.scanConfig(); + return this.order.map((sha) => this.byHash.get(sha)!); + } +} + +function addSource(report: WasmModuleReport, source: WasmSource): void { + for (const s of report.sources) { + if (s.kind === source.kind && s.file === source.file && s.line === source.line) return; + } + report.sources.push(source); +} + +function wasmLiteral(node: ts.Expression): string | null { + if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) return null; + return node.text.endsWith(".wasm") ? node.text : null; +} + +function isDirname(node: ts.Expression): boolean { + return ts.isIdentifier(node) && node.text === "__dirname"; +} + +function isImportMetaUrl(node: ts.Expression): boolean { + return ( + ts.isPropertyAccessExpression(node) && + node.name.text === "url" && + ts.isMetaProperty(node.expression) + ); +} + +/** `require`, a `createRequire(...)` call, or a binding named like one. */ +function isRequireLike(node: ts.Expression): boolean { + if (ts.isIdentifier(node)) return node.text === "require" || /require$/i.test(node.text); + if (ts.isCallExpression(node)) { + const callee = node.expression; + return ( + (ts.isIdentifier(callee) && callee.text === "createRequire") || + (ts.isPropertyAccessExpression(callee) && callee.name.text === "createRequire") + ); + } + return false; +} + +/** The bytes of an array literal of integer literals, or null. */ +function byteArrayLiteral(node: ts.ArrayLiteralExpression): Uint8Array | null { + const elements = node.elements; + const out = new Uint8Array(elements.length); + for (let i = 0; i < elements.length; i++) { + const element = elements[i]!; + if (!ts.isNumericLiteral(element)) return null; + const value = Number(element.text); + if (!Number.isInteger(value) || value < 0 || value > 255) return null; + out[i] = value; + } + 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 { + const marker = "/node_modules/"; + const at = file.lastIndexOf(marker); + if (at < 0) return null; + const rest = file.slice(at + marker.length); + const segments = rest.split("/"); + const take = segments[0]!.startsWith("@") ? 2 : 1; + if (segments.length <= take) return null; + return file.slice(0, at + marker.length) + segments.slice(0, take).join("/"); +} + +function collectWasmFiles(dir: string, depth: number, out: string[]): void { + const entries = trackedAccessibleEntries(dir); + if (entries === null) return; + for (const name of entries.files) if (name.endsWith(".wasm")) out.push(join(dir, name)); + if (depth >= PACKAGE_SCAN_DEPTH) return; + for (const name of entries.directories) { + if (name === "node_modules" || name.startsWith(".")) continue; + collectWasmFiles(join(dir, name), depth + 1, out); + } +} + +function nearestPackageJson(dir: string): string | null { + for (;;) { + const candidate = join(dir, "package.json"); + if (trackedFileExists(candidate)) return candidate; + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + +/** Every WebAssembly module the program's sources, packages and + * configuration make visible to the build, in discovery order. */ +export function discoverWasmModules(input: WasmDiscoveryInput): WasmModuleReport[] { + return new Discovery(input).run(); +} diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index f5313f12d..a167254c2 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -466,6 +466,13 @@ "status": "static", "note": "recognized module (bare and node:-prefixed specifiers)" }, + { + "id": "node-builtin.constants", + "kind": "node-builtin", + "name": "constants", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, { "id": "node-builtin.crypto", "kind": "node-builtin", @@ -2107,6 +2114,56 @@ "status": "static", "note": "shared version dictionary containing node and openssl; other components are absent unless defined by the program" }, + { + "id": "node-builtin.punycode", + "kind": "node-builtin", + "name": "punycode", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, + { + "id": "node-builtin.punycode.decode", + "kind": "node-builtin", + "name": "punycode.decode", + "status": "static" + }, + { + "id": "node-builtin.punycode.encode", + "kind": "node-builtin", + "name": "punycode.encode", + "status": "static" + }, + { + "id": "node-builtin.punycode.toASCII", + "kind": "node-builtin", + "name": "punycode.toASCII", + "status": "static" + }, + { + "id": "node-builtin.punycode.toUnicode", + "kind": "node-builtin", + "name": "punycode.toUnicode", + "status": "static" + }, + { + "id": "node-builtin.punycode.ucs2.decode", + "kind": "node-builtin", + "name": "punycode.ucs2.decode", + "status": "static" + }, + { + "id": "node-builtin.punycode.ucs2.encode", + "kind": "node-builtin", + "name": "punycode.ucs2.encode", + "status": "static" + }, + { + "id": "node-builtin.punycode.version", + "kind": "node-builtin", + "name": "punycode.version", + "status": "static", + "note": "constant value read" + }, { "id": "node-builtin.querystring", "kind": "node-builtin", @@ -4407,7 +4464,7 @@ "kind": "stdlib", "name": "Map.prototype.entries", "status": "static", - "note": "live native iterators support next(), for...of, and drains; iterator helper methods are refused" + "note": "live native iterators support next(), for...of, drains, and the iterator helper methods" }, { "id": "stdlib.map.forEach", @@ -4432,7 +4489,7 @@ "kind": "stdlib", "name": "Map.prototype.keys", "status": "static", - "note": "live native iterators support next(), for...of, and drains; iterator helper methods are refused" + "note": "live native iterators support next(), for...of, drains, and the iterator helper methods" }, { "id": "stdlib.map.set", @@ -4445,7 +4502,7 @@ "kind": "stdlib", "name": "Map.prototype.values", "status": "static", - "note": "live native iterators support next(), for...of, and drains; iterator helper methods are refused" + "note": "live native iterators support next(), for...of, drains, and the iterator helper methods" }, { "id": "stdlib.math.E", @@ -5314,7 +5371,7 @@ "kind": "stdlib", "name": "Set.prototype.entries", "status": "static", - "note": "live native iterators support next(), for...of, and drains; iterator helper methods are refused" + "note": "live native iterators support next(), for...of, drains, and the iterator helper methods" }, { "id": "stdlib.set.forEach", @@ -5361,7 +5418,7 @@ "kind": "stdlib", "name": "Set.prototype.keys", "status": "static", - "note": "live native iterators support next(), for...of, and drains; iterator helper methods are refused" + "note": "live native iterators support next(), for...of, drains, and the iterator helper methods" }, { "id": "stdlib.set.symmetricDifference", @@ -5382,7 +5439,7 @@ "kind": "stdlib", "name": "Set.prototype.values", "status": "static", - "note": "live native iterators support next(), for...of, and drains; iterator helper methods are refused" + "note": "live native iterators support next(), for...of, drains, and the iterator helper methods" }, { "id": "stdlib.sharedArrayBuffer", @@ -5573,6 +5630,14 @@ "status": "static", "note": "the lowered call form takes no arguments" }, + { + "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)" + }, { "id": "syntax.async-generators", "kind": "syntax", diff --git a/packages/compiler/test/class-early-field-reads.test.ts b/packages/compiler/test/class-early-field-reads.test.ts deleted file mode 100644 index c602a2f42..000000000 --- a/packages/compiler/test/class-early-field-reads.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -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, validateModule } from "../src/index.js"; -import type { IrModule } from "../src/ir/ir.js"; - -async function lower(source: string, file = "main.ts"): Promise { - const dir = await mkdtemp(join(tmpdir(), "scriptc-early-field-reads-")); - try { - const entry = join(dir, file), - output = join(dir, "main.ir.json"); - await writeFile(entry, source); - const result = await compile(entry, { outDir: dir, outPath: output, outputKind: "ir" }); - if (!result.ok) - throw new Error(result.diagnostics.map((item) => `${item.code}: ${item.message}`).join("\n")); - const module = deserializeModule(await readFile(output, "utf8")); - expect(validateModule(module)).toEqual([]); - return module; - } finally { - await rm(dir, { recursive: true, force: true }); - } -} - -/** Field name → IR kind for a class. */ -function fields(module: IrModule, name: string): string { - const cls = module.classes.find((candidate) => candidate.name === name); - expect(cls, name).toBeDefined(); - return cls!.fields - .filter((field) => !field.name.startsWith("%")) - .map((field) => `${field.name}:${field.type.kind}`) - .join(" "); -} - -test("fields assigned after construction can call an override admit undefined", async () => { - const module = await lower(` -abstract class Base { - constructor() { - this.describe(); - } - abstract describe(): void; -} -class Panel extends Base { - label: string; - width: number; - count = 0; - constructor() { - super(); - this.label = "panel"; - this.width = 2; - } - describe(): void { - console.log(this.label, this.width, this.count); - } -} -class Plain { - label: string; - width: number; - constructor() { - this.label = "plain"; - this.width = 1; - this.describe(); - } - describe(): void { - console.log(this.label, this.width); - } -} -new Panel(); -new Plain(); -`); - expect(fields(module, "Panel")).toBe("label:union width:union count:union"); - expect(fields(module, "Plain")).toBe("label:string width:f64"); -}); - -test("JavaScript fields created after the constructor calls a method admit undefined", async () => { - const module = await lower( - ` -class Widget { - constructor() { - this.ready = true; - this.render(); - this.title = "widget"; - } - render() { - console.log(this.ready, this.title); - } -} -new Widget(); -`, - "main.js", - ); - expect(fields(module, "Widget")).toBe("ready:bool title:union"); -}); diff --git a/packages/compiler/test/gen-wasm-spec-cases.mjs b/packages/compiler/test/gen-wasm-spec-cases.mjs new file mode 100644 index 000000000..068053f0e --- /dev/null +++ b/packages/compiler/test/gen-wasm-spec-cases.mjs @@ -0,0 +1,69 @@ +/* Regenerates wasm-spec-cases.txt: the binary modules of the WebAssembly + * specification test suite at the feature level the decoder validates + * (src/wasm/binary.ts), each with the suite's verdict and V8's. + * + * node gen-wasm-spec-cases.mjs --wast2json /bin/wast2json \ + * --spec /test/core \ + * --extended-const /test/core > wasm-spec-cases.txt + * + * Inputs: every .wast of the 2.0 core suite except simd/ (SIMD is outside + * the translatable set; the decoder only reports it), plus the extended + * constant expression proposal's global, data and elem tests. Modules given + * as text for assert_malformed are skipped (they test the text format). + * Identical modules are kept once, at their first location. + * + * Line format: : , where a + * verdict is "valid", "invalid" or "malformed" (V8: "valid" or "invalid"). + * The suite's tests are Apache-2.0 licensed (WebAssembly Community Group). */ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { parseArgs } from "node:util"; + +const { values } = parseArgs({ + options: { + wast2json: { type: "string" }, + spec: { type: "string" }, + "extended-const": { type: "string" }, + }, +}); +const inputs = []; +for (const name of readdirSync(values.spec).sort()) { + if (name.endsWith(".wast")) inputs.push({ label: name, path: join(values.spec, name) }); +} +for (const name of ["global.wast", "data.wast", "elem.wast"]) { + inputs.push({ label: `extended-const/${name}`, path: join(values["extended-const"], name) }); +} +const seen = new Set(); +const lines = [ + "# WebAssembly spec test modules for src/wasm/binary.ts; generated by gen-wasm-spec-cases.mjs.", + "# Sources: WebAssembly/spec wg-2.0 test/core (without simd/) and WebAssembly/extended-const", + "# test/core global, data and elem, converted with wabt 1.0.37 wast2json (Apache-2.0).", + "# : ", +]; +for (const { label, path } of inputs) { + const dir = mkdtempSync(join(tmpdir(), "wasm-spec-")); + try { + execFileSync(values.wast2json, [path, "-o", join(dir, "x.json")], { stdio: "pipe" }); + const json = JSON.parse(readFileSync(join(dir, "x.json"), "utf8")); + for (const command of json.commands) { + let verdict; + if (["module", "assert_unlinkable", "assert_uninstantiable"].includes(command.type)) + verdict = "valid"; + else if (command.type === "assert_invalid") verdict = "invalid"; + else if (command.type === "assert_malformed") verdict = "malformed"; + else continue; + if (command.module_type === "text" || !command.filename?.endsWith(".wasm")) continue; + const bytes = readFileSync(join(dir, command.filename)); + const b64 = bytes.toString("base64"); + if (seen.has(b64)) continue; + seen.add(b64); + const v8 = WebAssembly.validate(bytes) ? "valid" : "invalid"; + lines.push(`${verdict} ${v8} ${label}:${command.line} ${b64}`); + } + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} +process.stdout.write(`${lines.join("\n")}\n`); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5633-zlib-typed-options.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5633-zlib-typed-options.ts.json new file mode 100644 index 000000000..f4d9673d5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5633-zlib-typed-options.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5633-zlib-typed-options.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5683-date-local-fields-tz.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5683-date-local-fields-tz.ts.json new file mode 100644 index 000000000..7ac133650 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5683-date-local-fields-tz.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5683-date-local-fields-tz.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5768-crypto-hash-unknown.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5768-crypto-hash-unknown.ts.json new file mode 100644 index 000000000..ba6370e68 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5768-crypto-hash-unknown.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5768-crypto-hash-unknown.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5769-crypto-hash-unknown.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5769-crypto-hash-unknown.cjs.json new file mode 100644 index 000000000..60e1632e9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5769-crypto-hash-unknown.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5769-crypto-hash-unknown.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5774-crypto-handle-long-callee.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5774-crypto-handle-long-callee.cjs.json new file mode 100644 index 000000000..d2f21dbe2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5774-crypto-handle-long-callee.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5774-crypto-handle-long-callee.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5908-iterator-helper-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5908-iterator-helper-objects.ts.json new file mode 100644 index 000000000..5aa23f5d9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5908-iterator-helper-objects.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5908-iterator-helper-objects.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5909-iterator-helper-objects-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5909-iterator-helper-objects-js.js.json new file mode 100644 index 000000000..fd3454670 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5909-iterator-helper-objects-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5909-iterator-helper-objects-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5919-record-unions-shared-arms.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5919-record-unions-shared-arms.ts.json new file mode 100644 index 000000000..bfb23e115 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5919-record-unions-shared-arms.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5919-record-unions-shared-arms.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5930-record-union-pointers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5930-record-union-pointers.ts.json new file mode 100644 index 000000000..e6e3632f5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5930-record-union-pointers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5930-record-union-pointers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5931-record-union-unit-arms.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5931-record-union-unit-arms.ts.json new file mode 100644 index 000000000..53d531568 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5931-record-union-unit-arms.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5931-record-union-unit-arms.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5932-record-union-trees.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5932-record-union-trees.ts.json new file mode 100644 index 000000000..ee4281193 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5932-record-union-trees.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5932-record-union-trees.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5933-record-union-conversions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5933-record-union-conversions.ts.json new file mode 100644 index 000000000..a1fcec317 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5933-record-union-conversions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5933-record-union-conversions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5934-record-union-set-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5934-record-union-set-members.ts.json new file mode 100644 index 000000000..98b0ac1f4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5934-record-union-set-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5934-record-union-set-members.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5943-fields-read-before-assignment.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5943-fields-read-before-assignment.ts.json deleted file mode 100644 index d2dcdcc24..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5943-fields-read-before-assignment.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/5943-fields-read-before-assignment.ts" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5944-js-fields-read-before-assignment.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5944-js-fields-read-before-assignment.js.json deleted file mode 100644 index e9800b71d..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5944-js-fields-read-before-assignment.js.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/5944-js-fields-read-before-assignment.js" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5973-buffer-concat-untyped.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5973-buffer-concat-untyped.js.json new file mode 100644 index 000000000..3fffd75c2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5973-buffer-concat-untyped.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5973-buffer-concat-untyped.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5976-builtin-class-aliases.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5976-builtin-class-aliases.ts.json new file mode 100644 index 000000000..f83d23fc7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5976-builtin-class-aliases.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5976-builtin-class-aliases.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5977-builtin-class-aliases.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5977-builtin-class-aliases.cjs.json new file mode 100644 index 000000000..99d07e4cd --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5977-builtin-class-aliases.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5977-builtin-class-aliases.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5978-builtin-class-alias-modules/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5978-builtin-class-alias-modules/main.ts.json new file mode 100644 index 000000000..fd6bb94f5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5978-builtin-class-alias-modules/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/5978-builtin-class-alias-modules/classes.ts", + "/tests/corpus/5978-builtin-class-alias-modules/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5979-writable-instanceof-duplex.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5979-writable-instanceof-duplex.ts.json new file mode 100644 index 000000000..eca80e7cf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5979-writable-instanceof-duplex.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5979-writable-instanceof-duplex.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5980-cycle-components.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5980-cycle-components.ts.json new file mode 100644 index 000000000..cdbf47751 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5980-cycle-components.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5980-cycle-components.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5993-array-length-loop-resizing.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5993-array-length-loop-resizing.ts.json new file mode 100644 index 000000000..e4cf027ce --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5993-array-length-loop-resizing.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5993-array-length-loop-resizing.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5994-array-length-sparse-loops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5994-array-length-sparse-loops.ts.json new file mode 100644 index 000000000..6289e8a21 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5994-array-length-sparse-loops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5994-array-length-sparse-loops.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5995-array-dense-loop-boundaries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5995-array-dense-loop-boundaries.ts.json new file mode 100644 index 000000000..d0afcf505 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5995-array-dense-loop-boundaries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5995-array-dense-loop-boundaries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5996-rejections-between-macrotasks.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5996-rejections-between-macrotasks.cjs.json new file mode 100644 index 000000000..0e719fe78 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5996-rejections-between-macrotasks.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5996-rejections-between-macrotasks.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5997-frame-records-in-record-unions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5997-frame-records-in-record-unions.ts.json new file mode 100644 index 000000000..7e3fb1c50 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5997-frame-records-in-record-unions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5997-frame-records-in-record-unions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6000-json-stringify-class-instances.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6000-json-stringify-class-instances.ts.json new file mode 100644 index 000000000..93c106c07 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6000-json-stringify-class-instances.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6000-json-stringify-class-instances.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6001-json-stringify-js-class-instances.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6001-json-stringify-js-class-instances.js.json new file mode 100644 index 000000000..8ecb709f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6001-json-stringify-js-class-instances.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6001-json-stringify-js-class-instances.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6051-generic-class-factory-results.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6051-generic-class-factory-results.ts.json new file mode 100644 index 000000000..fc148dcea --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6051-generic-class-factory-results.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6051-generic-class-factory-results.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6100-record-get-or-create.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6100-record-get-or-create.ts.json new file mode 100644 index 000000000..b7c18a8a4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6100-record-get-or-create.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6100-record-get-or-create.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6101-record-get-or-create-unchecked/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6101-record-get-or-create-unchecked/main.ts.json new file mode 100644 index 000000000..dcb5b192f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6101-record-get-or-create-unchecked/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6101-record-get-or-create-unchecked/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6103-typeof-builtin-bindings.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6103-typeof-builtin-bindings.cjs.json new file mode 100644 index 000000000..008914d85 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6103-typeof-builtin-bindings.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6103-typeof-builtin-bindings.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6104-typeof-builtin-bindings.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6104-typeof-builtin-bindings.mjs.json new file mode 100644 index 000000000..b6a8a48e2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6104-typeof-builtin-bindings.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6104-typeof-builtin-bindings.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6105-cjs-top-level-this/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6105-cjs-top-level-this/main.js.json new file mode 100644 index 000000000..ef768b4ce --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6105-cjs-top-level-this/main.js.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6105-cjs-top-level-this/lib.js", + "/tests/corpus/6105-cjs-top-level-this/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6106-esm-top-level-this.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6106-esm-top-level-this.mjs.json new file mode 100644 index 000000000..ad427bd7f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6106-esm-top-level-this.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6106-esm-top-level-this.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs.json new file mode 100644 index 000000000..0a5ce66b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6120-type-only-import-graph/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6120-type-only-import-graph/main.ts.json new file mode 100644 index 000000000..979391c1c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6120-type-only-import-graph/main.ts.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/corpus/6120-type-only-import-graph/mixed.ts", + "/tests/corpus/6120-type-only-import-graph/flags.ts", + "/tests/corpus/6120-type-only-import-graph/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6121-type-specifier-edges/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6121-type-specifier-edges/main.ts.json new file mode 100644 index 000000000..c0f3f653c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6121-type-specifier-edges/main.ts.json @@ -0,0 +1,10 @@ +{ + "order": [ + "/tests/corpus/6121-type-specifier-edges/config.ts", + "/tests/corpus/6121-type-specifier-edges/config-barrel.ts", + "/tests/corpus/6121-type-specifier-edges/b.ts", + "/tests/corpus/6121-type-specifier-edges/a.ts", + "/tests/corpus/6121-type-specifier-edges/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6122-punycode-conversions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6122-punycode-conversions.ts.json new file mode 100644 index 000000000..b15e4fe13 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6122-punycode-conversions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6122-punycode-conversions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6124-punycode-require-warnings.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6124-punycode-require-warnings.cjs.json new file mode 100644 index 000000000..dfd4aa912 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6124-punycode-require-warnings.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6124-punycode-require-warnings.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6125-constants-module.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6125-constants-module.cjs.json new file mode 100644 index 000000000..ef8c7f2bf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6125-constants-module.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6125-constants-module.cjs" + ], + "diags": [] +} 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 new file mode 100644 index 000000000..5de83361a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json @@ -0,0 +1,18 @@ +{ + "order": [ + "/tests/corpus/6126-punycode-dependency/main.cjs" + ], + "diags": [ + { + "code": "SC1010", + "message": "the 'puny-dep' module is not supported yet", + "loc": { + "file": "/tests/corpus/6126-punycode-dependency/main.cjs", + "start": 347, + "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" + } + ] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6150-cjs-inline-require/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6150-cjs-inline-require/main.js.json new file mode 100644 index 000000000..5f1e322f7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6150-cjs-inline-require/main.js.json @@ -0,0 +1,20 @@ +{ + "order": [ + "/tests/corpus/6150-cjs-inline-require/lib.js", + "/tests/corpus/6150-cjs-inline-require/inner.js", + "/tests/corpus/6150-cjs-inline-require/reexport-inline.js", + "/tests/corpus/6150-cjs-inline-require/reexport-binding.js", + "/tests/corpus/6150-cjs-inline-require/type-error.js", + "/tests/corpus/6150-cjs-inline-require/pick.js", + "/tests/corpus/6150-cjs-inline-require/registry.js", + "/tests/corpus/6150-cjs-inline-require/make-logger.js", + "/tests/corpus/6150-cjs-inline-require/point.js", + "/tests/corpus/6150-cjs-inline-require/shapes.js", + "/tests/corpus/6150-cjs-inline-require/table.js", + "/tests/corpus/6150-cjs-inline-require/verify.js", + "/tests/corpus/6150-cjs-inline-require/fallback.js", + "/tests/corpus/6150-cjs-inline-require/main.js", + "/tests/corpus/6150-cjs-inline-require/late.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6151-cjs-export-chains/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6151-cjs-export-chains/main.js.json new file mode 100644 index 000000000..883aa872a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6151-cjs-export-chains/main.js.json @@ -0,0 +1,10 @@ +{ + "order": [ + "/tests/corpus/6151-cjs-export-chains/chain.js", + "/tests/corpus/6151-cjs-export-chains/constants.js", + "/tests/corpus/6151-cjs-export-chains/re.js", + "/tests/corpus/6151-cjs-export-chains/sem.js", + "/tests/corpus/6151-cjs-export-chains/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6152-cjs-tsc-interop/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6152-cjs-tsc-interop/main.js.json new file mode 100644 index 000000000..19a1b20de --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6152-cjs-tsc-interop/main.js.json @@ -0,0 +1,9 @@ +{ + "order": [ + "/tests/corpus/6152-cjs-tsc-interop/plain.js", + "/tests/corpus/6152-cjs-tsc-interop/stamped.js", + "/tests/corpus/6152-cjs-tsc-interop/fn.js", + "/tests/corpus/6152-cjs-tsc-interop/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6153-cjs-exports-object/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6153-cjs-exports-object/main.js.json new file mode 100644 index 000000000..070b131e3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6153-cjs-exports-object/main.js.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6153-cjs-exports-object/lib.js", + "/tests/corpus/6153-cjs-exports-object/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6154-cjs-require-nested-destructure/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6154-cjs-require-nested-destructure/main.js.json new file mode 100644 index 000000000..ee9f71d22 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6154-cjs-require-nested-destructure/main.js.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/corpus/6154-cjs-require-nested-destructure/errors.js", + "/tests/corpus/6154-cjs-require-nested-destructure/constants.js", + "/tests/corpus/6154-cjs-require-nested-destructure/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6155-cjs-export-table-methods/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6155-cjs-export-table-methods/main.js.json new file mode 100644 index 000000000..9ac5f0e41 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6155-cjs-export-table-methods/main.js.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/corpus/6155-cjs-export-table-methods/more.js", + "/tests/corpus/6155-cjs-export-table-methods/lib.js", + "/tests/corpus/6155-cjs-export-table-methods/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6156-cjs-function-members-from-ts/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6156-cjs-function-members-from-ts/main.ts.json new file mode 100644 index 000000000..e73e603e3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6156-cjs-function-members-from-ts/main.ts.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/corpus/6156-cjs-function-members-from-ts/status.cjs", + "/tests/corpus/6156-cjs-function-members-from-ts/consumer.cjs", + "/tests/corpus/6156-cjs-function-members-from-ts/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6160-number-radix-typed.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6160-number-radix-typed.ts.json new file mode 100644 index 000000000..6df464b2b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6160-number-radix-typed.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6160-number-radix-typed.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6161-dispose-observer-gates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6161-dispose-observer-gates.ts.json new file mode 100644 index 000000000..1387cc325 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6161-dispose-observer-gates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6161-dispose-observer-gates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6162-dispose-observer-weak-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6162-dispose-observer-weak-keys.ts.json new file mode 100644 index 000000000..ab4838284 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6162-dispose-observer-weak-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6162-dispose-observer-weak-keys.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6163-dispose-observer-workers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6163-dispose-observer-workers.ts.json new file mode 100644 index 000000000..327aa105f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6163-dispose-observer-workers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6163-dispose-observer-workers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6164-regex-replace-typed-callback.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6164-regex-replace-typed-callback.ts.json new file mode 100644 index 000000000..8ca65275f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6164-regex-replace-typed-callback.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6164-regex-replace-typed-callback.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6165-regex-replace-callback-throws.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6165-regex-replace-callback-throws.ts.json new file mode 100644 index 000000000..0f4b99484 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6165-regex-replace-callback-throws.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6165-regex-replace-callback-throws.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6170-flight-dev-stacks-dead.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6170-flight-dev-stacks-dead.ts.json new file mode 100644 index 000000000..f78bd38f5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6170-flight-dev-stacks-dead.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6170-flight-dev-stacks-dead.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6171-error-stack-capture-sites.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6171-error-stack-capture-sites.ts.json new file mode 100644 index 000000000..b4cdd2191 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6171-error-stack-capture-sites.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6171-error-stack-capture-sites.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6172-unreachable-statements.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6172-unreachable-statements.ts.json new file mode 100644 index 000000000..3ecbd301c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6172-unreachable-statements.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6172-unreachable-statements.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6180-interface-overload-events.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6180-interface-overload-events.ts.json new file mode 100644 index 000000000..28b4381d2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6180-interface-overload-events.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6180-interface-overload-events.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6181-interface-overload-arity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6181-interface-overload-arity.ts.json new file mode 100644 index 000000000..919c56254 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6181-interface-overload-arity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6181-interface-overload-arity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6182-interface-overload-types.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6182-interface-overload-types.ts.json new file mode 100644 index 000000000..249d2084e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6182-interface-overload-types.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6182-interface-overload-types.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6183-interface-overload-flow.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6183-interface-overload-flow.ts.json new file mode 100644 index 000000000..b63d2780c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6183-interface-overload-flow.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6183-interface-overload-flow.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-optional-chain-unchecked-receivers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-optional-chain-unchecked-receivers.ts.json new file mode 100644 index 000000000..338ab8c6e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-optional-chain-unchecked-receivers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6230-optional-chain-unchecked-receivers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/error-js-accessor-name-message.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/error-js-accessor-name-message.js.json new file mode 100644 index 000000000..e4c388b08 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/error-js-accessor-name-message.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/error-js-accessor-name-message.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-builder-string-method-names/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-builder-string-method-names/main.ts.json new file mode 100644 index 000000000..d1f487f89 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-builder-string-method-names/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/js-builder-string-method-names/builder.js", + "/tests/corpus/js-builder-string-method-names/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-callable-builder.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-callable-builder.js.json new file mode 100644 index 000000000..8f1025c47 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-callable-builder.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/js-callable-builder.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-class-constructor-returns/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-class-constructor-returns/main.ts.json new file mode 100644 index 000000000..e05f1760c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-class-constructor-returns/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/js-class-constructor-returns/factory.js", + "/tests/corpus/js-class-constructor-returns/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-function-prototype-value.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-function-prototype-value.js.json new file mode 100644 index 000000000..b0fa827e1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-function-prototype-value.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/js-function-prototype-value.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-number-parse-dynamic.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-number-parse-dynamic.js.json new file mode 100644 index 000000000..608e24d40 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-number-parse-dynamic.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/js-number-parse-dynamic.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-process-destructure.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-process-destructure.mjs.json new file mode 100644 index 000000000..87d43324e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-process-destructure.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/js-process-destructure.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-reshaped-object-literal.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-reshaped-object-literal.js.json new file mode 100644 index 000000000..939240b2c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/js-reshaped-object-literal.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/js-reshaped-object-literal.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/scratch-string-slices.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/scratch-string-slices.ts.json new file mode 100644 index 000000000..5145c33d1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/scratch-string-slices.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/scratch-string-slices.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/truthy-object-logical.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/truthy-object-logical.ts.json new file mode 100644 index 000000000..96661c630 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/truthy-object-logical.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/truthy-object-logical.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-feature-probe.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-feature-probe.js.json new file mode 100644 index 000000000..70988ec5f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-feature-probe.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-feature-probe.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-type-mentions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-type-mentions.ts.json new file mode 100644 index 000000000..b86f1bd8a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-type-mentions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-type-mentions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-class-alias-escape.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-class-alias-escape.ts.json new file mode 100644 index 000000000..f56d058f1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-class-alias-escape.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/builtin-class-alias-escape.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/json-class-fields.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/json-class-fields.ts.json new file mode 100644 index 000000000..ee3ed6bad --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/json-class-fields.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/json-class-fields.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-brand.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-brand.ts.json new file mode 100644 index 000000000..e17ea3bf5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-brand.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/local-class-type-query-brand.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-js.js.json new file mode 100644 index 000000000..6bf774cd8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/local-class-type-query-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query.ts.json new file mode 100644 index 000000000..3c89fba9f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/local-class-type-query.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overloaded-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overloaded-members.ts.json new file mode 100644 index 000000000..33ea8a976 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overloaded-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/overloaded-members.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/speculative-function-refusal.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/speculative-function-refusal.ts.json new file mode 100644 index 000000000..e24349766 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/speculative-function-refusal.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/speculative-function-refusal.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/webassembly-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/webassembly-fences.ts.json new file mode 100644 index 000000000..feb8cb469 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/webassembly-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/webassembly-fences.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/chalk5/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/chalk5/main.ts.json new file mode 100644 index 000000000..065c96347 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/chalk5/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/chalk5/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/wasm-spec-cases.txt b/packages/compiler/test/wasm-spec-cases.txt new file mode 100644 index 000000000..16d2ae2b6 --- /dev/null +++ b/packages/compiler/test/wasm-spec-cases.txt @@ -0,0 +1,3017 @@ +# WebAssembly spec test modules for src/wasm/binary.ts; generated by gen-wasm-spec-cases.mjs. +# Sources: WebAssembly/spec wg-2.0 test/core (without simd/) and WebAssembly/extended-const +# test/core global, data and elem, converted with wabt 1.0.37 wast2json (Apache-2.0). +# : +valid valid address.wast:3 AGFzbQEAAAABCgJgAX8Bf2ABfwADHx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEFAwEAAQfNAh4IOHVfZ29vZDEAAAg4dV9nb29kMgABCDh1X2dvb2QzAAIIOHVfZ29vZDQAAwg4dV9nb29kNQAECDhzX2dvb2QxAAUIOHNfZ29vZDIABgg4c19nb29kMwAHCDhzX2dvb2Q0AAgIOHNfZ29vZDUACQkxNnVfZ29vZDEACgkxNnVfZ29vZDIACwkxNnVfZ29vZDMADAkxNnVfZ29vZDQADQkxNnVfZ29vZDUADgkxNnNfZ29vZDEADwkxNnNfZ29vZDIAEAkxNnNfZ29vZDMAEQkxNnNfZ29vZDQAEgkxNnNfZ29vZDUAEwgzMl9nb29kMQAUCDMyX2dvb2QyABUIMzJfZ29vZDMAFggzMl9nb29kNAAXCDMyX2dvb2Q1ABgGOHVfYmFkABkGOHNfYmFkABoHMTZ1X2JhZAAbBzE2c19iYWQAHAYzMl9iYWQAHQqKAh4HACAALQAACwcAIAAtAAALBwAgAC0AAQsHACAALQACCwcAIAAtABkLBwAgACwAAAsHACAALAAACwcAIAAsAAELBwAgACwAAgsHACAALAAZCwcAIAAvAQALBwAgAC8AAAsHACAALwABCwcAIAAvAQILBwAgAC8BGQsHACAALgEACwcAIAAuAAALBwAgAC4AAQsHACAALgECCwcAIAAuARkLBwAgACgCAAsHACAAKAAACwcAIAAoAAELBwAgACgBAgsHACAAKAIZCwwAIAAtAP////8PGgsMACAALAD/////DxoLDAAgAC8B/////w8aCwwAIAAuAf////8PGgsMACAAKAL/////DxoLCyABAEEACxphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg== +valid valid address.wast:223 AGFzbQEAAAABCgJgAX8BfmABfwADKyoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEFAwEAAQfZAyoIOHVfZ29vZDEAAAg4dV9nb29kMgABCDh1X2dvb2QzAAIIOHVfZ29vZDQAAwg4dV9nb29kNQAECDhzX2dvb2QxAAUIOHNfZ29vZDIABgg4c19nb29kMwAHCDhzX2dvb2Q0AAgIOHNfZ29vZDUACQkxNnVfZ29vZDEACgkxNnVfZ29vZDIACwkxNnVfZ29vZDMADAkxNnVfZ29vZDQADQkxNnVfZ29vZDUADgkxNnNfZ29vZDEADwkxNnNfZ29vZDIAEAkxNnNfZ29vZDMAEQkxNnNfZ29vZDQAEgkxNnNfZ29vZDUAEwkzMnVfZ29vZDEAFAkzMnVfZ29vZDIAFQkzMnVfZ29vZDMAFgkzMnVfZ29vZDQAFwkzMnVfZ29vZDUAGAkzMnNfZ29vZDEAGQkzMnNfZ29vZDIAGgkzMnNfZ29vZDMAGwkzMnNfZ29vZDQAHAkzMnNfZ29vZDUAHQg2NF9nb29kMQAeCDY0X2dvb2QyAB8INjRfZ29vZDMAIAg2NF9nb29kNAAhCDY0X2dvb2Q1ACIGOHVfYmFkACMGOHNfYmFkACQHMTZ1X2JhZAAlBzE2c19iYWQAJgczMnVfYmFkACcHMzJzX2JhZAAoBjY0X2JhZAApCvQCKgcAIAAxAAALBwAgADEAAAsHACAAMQABCwcAIAAxAAILBwAgADEAGQsHACAAMAAACwcAIAAwAAALBwAgADAAAQsHACAAMAACCwcAIAAwABkLBwAgADMBAAsHACAAMwAACwcAIAAzAAELBwAgADMBAgsHACAAMwEZCwcAIAAyAQALBwAgADIAAAsHACAAMgABCwcAIAAyAQILBwAgADIBGQsHACAANQIACwcAIAA1AAALBwAgADUAAQsHACAANQECCwcAIAA1AhkLBwAgADQCAAsHACAANAAACwcAIAA0AAELBwAgADQBAgsHACAANAIZCwcAIAApAwALBwAgACkAAAsHACAAKQABCwcAIAApAQILBwAgACkDGQsMACAAMQD/////DxoLDAAgADAA/////w8aCwwAIAAzAf////8PGgsMACAAMgH/////DxoLDAAgADUC/////w8aCwwAIAA0Av////8PGgsMACAAKQP/////DxoLCyABAEEACxphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg== +valid valid address.wast:514 AGFzbQEAAAABCgJgAX8BfWABfwADBwYAAAAAAAEFAwEAAQdBBggzMl9nb29kMQAACDMyX2dvb2QyAAEIMzJfZ29vZDMAAggzMl9nb29kNAADCDMyX2dvb2Q1AAQGMzJfYmFkAAUKNgYHACAAKgIACwcAIAAqAAALBwAgACoAAQsHACAAKgECCwcAIAAqAggLDAAgACoC/////w8aCwsSAQBBAAsMAAAAAAAAoH8BANB/ +valid valid address.wast:564 AGFzbQEAAAABCgJgAX8BfGABfwADBwYAAAAAAAEFAwEAAQdBBgg2NF9nb29kMQAACDY0X2dvb2QyAAEINjRfZ29vZDMAAgg2NF9nb29kNAADCDY0X2dvb2Q1AAQGNjRfYmFkAAUKNgYHACAAKwMACwcAIAArAAALBwAgACsAAQsHACAAKwECCwcAIAArAxILDAAgACsD/////w8aCwsgAQBBAAsaAAAAAAAAAAAAAAAAAAAAAPR/AQAAAAAA/H8= +valid valid align.wast:3 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALAAAGgs= +valid valid align.wast:4 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALQAAGgs= +valid valid align.wast:5 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALgEAGgs= +valid valid align.wast:6 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALwEAGgs= +valid valid align.wast:7 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKAIAGgs= +valid valid align.wast:8 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMAAAGgs= +valid valid align.wast:9 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMQAAGgs= +valid valid align.wast:10 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMgEAGgs= +valid valid align.wast:11 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMwEAGgs= +valid valid align.wast:12 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANAIAGgs= +valid valid align.wast:13 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANQIAGgs= +valid valid align.wast:14 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKQMAGgs= +valid valid align.wast:15 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKgIAGgs= +valid valid align.wast:16 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKwMAGgs= +valid valid align.wast:17 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE6AAAL +valid valid align.wast:18 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE7AQAL +valid valid align.wast:19 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE2AgAL +valid valid align.wast:20 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE8AAAL +valid valid align.wast:21 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE9AQAL +valid valid align.wast:22 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE+AgAL +valid valid align.wast:23 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE3AwAL +valid valid align.wast:24 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDgEMAEEAQwAAgD84AgAL +valid valid align.wast:25 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKEgEQAEEARAAAAAAAAPA/OQMACw== +invalid invalid align.wast:306 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALAEAGgs= +invalid invalid align.wast:310 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALQEAGgs= +invalid invalid align.wast:314 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALgIAGgs= +invalid invalid align.wast:318 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALwIAGgs= +invalid invalid align.wast:322 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKAMAGgs= +invalid invalid align.wast:326 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMAEAGgs= +invalid invalid align.wast:330 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMQEAGgs= +invalid invalid align.wast:334 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMgIAGgs= +invalid invalid align.wast:338 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMwIAGgs= +invalid invalid align.wast:342 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANAMAGgs= +invalid invalid align.wast:346 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANQMAGgs= +invalid invalid align.wast:350 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKQQAGgs= +invalid invalid align.wast:354 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKgMAGgs= +invalid invalid align.wast:358 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKwQAGgs= +invalid invalid align.wast:420 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQA6AQAL +invalid invalid align.wast:424 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQA7AgAL +invalid invalid align.wast:428 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQA2AwAL +invalid invalid align.wast:432 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgA8AQAL +invalid invalid align.wast:436 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgA9AgAL +invalid invalid align.wast:440 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgA+AwAL +invalid invalid align.wast:444 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgA3BAAL +invalid invalid align.wast:448 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDgEMAEEAQwAAAAA4AwAL +invalid invalid align.wast:452 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKEgEQAEEARAAAAAAAAAAAOQQACw== +valid valid align.wast:458 AGFzbQEAAAABFwRgAX8BfWABfwF8YAJ/fwF/YAJ/fwF+AwUEAAECAwUDAQABB00EEGYzMl9hbGlnbl9zd2l0Y2gAABBmNjRfYWxpZ25fc3dpdGNoAAEQaTMyX2FsaWduX3N3aXRjaAACEGk2NF9hbGlnbl9zd2l0Y2gAAwr7CARjAQJ9QwAAIEEhAQJAAkACQAJAAkAgAA4EAAECAwQLQQAgATgCAEEAKgIAIQIMAwtBACABOAAAQQAqAAAhAgwCC0EAIAE4AQBBACoBACECDAELQQAgATgCAEEAKgIAIQILIAILewECfEQAAAAAAAAkQCEBAkACQAJAAkACQAJAIAAOBQABAgMEBQtBACABOQMAQQArAwAhAgwEC0EAIAE5AABBACsAACECDAMLQQAgATkBAEEAKwEAIQIMAgtBACABOQIAQQArAgAhAgwBC0EAIAE5AwBBACsDACECCyACC+ICAQJ/QQohAgJAAkACQAJAAkACQCAADgUAAQIDBAULIAFBAEYEQEEAIAI6AABBACwAACEDCyABQQFGBEBBACACOgAAQQAsAAAhAwsMBAsgAUEARgRAQQAgAjoAAEEALQAAIQMLIAFBAUYEQEEAIAI6AABBAC0AACEDCwwDCyABQQBGBEBBACACOwEAQQAuAQAhAwsgAUEBRgRAQQAgAjsAAEEALgAAIQMLIAFBAkYEQEEAIAI7AQBBAC4BACEDCwwCCyABQQBGBEBBACACOwEAQQAvAQAhAwsgAUEBRgRAQQAgAjsAAEEALwAAIQMLIAFBAkYEQEEAIAI7AQBBAC8BACEDCwwBCyABQQBGBEBBACACNgIAQQAoAgAhAwsgAUEBRgRAQQAgAjYAAEEAKAAAIQMLIAFBAkYEQEEAIAI2AQBBACgBACEDCyABQQRGBEBBACACNgIAQQAoAgAhAwsLIAMLtAQBAn5CCiECAkACQAJAAkACQAJAAkACQCAADgcAAQIDBAUGBwsgAUEARgRAQQAgAjwAAEEAMAAAIQMLIAFBAUYEQEEAIAI8AABBADAAACEDCwwGCyABQQBGBEBBACACPAAAQQAxAAAhAwsgAUEBRgRAQQAgAjwAAEEAMQAAIQMLDAULIAFBAEYEQEEAIAI9AQBBADIBACEDCyABQQFGBEBBACACPQAAQQAyAAAhAwsgAUECRgRAQQAgAj0BAEEAMgEAIQMLDAQLIAFBAEYEQEEAIAI9AQBBADMBACEDCyABQQFGBEBBACACPQAAQQAzAAAhAwsgAUECRgRAQQAgAj0BAEEAMwEAIQMLDAMLIAFBAEYEQEEAIAI+AgBBADQCACEDCyABQQFGBEBBACACPgAAQQA0AAAhAwsgAUECRgRAQQAgAj4BAEEANAEAIQMLIAFBBEYEQEEAIAI+AgBBADQCACEDCwwCCyABQQBGBEBBACACPgIAQQA1AgAhAwsgAUEBRgRAQQAgAj4AAEEANQAAIQMLIAFBAkYEQEEAIAI+AQBBADUBACEDCyABQQRGBEBBACACPgIAQQA1AgAhAwsMAQsgAUEARgRAQQAgAjcDAEEAKQMAIQMLIAFBAUYEQEEAIAI3AABBACkAACEDCyABQQJGBEBBACACNwEAQQApAQAhAwsgAUEERgRAQQAgAjcCAEEAKQIAIQMLIAFBCEYEQEEAIAI3AwBBACkDACEDCwsgAws= +valid valid align.wast:854 AGFzbQEAAAABCwJgAn9+AGABfwF/AwMCAAEFAwEAAQcQAgVzdG9yZQAABGxvYWQAAQoTAgkAIAAgATcCAAsHACAAKAIACw== +invalid invalid align.wast:873 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEEAKB8AGgs= +malformed invalid align.wast:892 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEEAKCAAGgs= +malformed invalid align.wast:911 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEEAKCEAGgs= +malformed invalid align.wast:930 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEEAKD8AGgs= +malformed invalid align.wast:949 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEEAKEAAGgs= +malformed invalid align.wast:968 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEEAKEEAGgs= +valid valid binary-leb128.wast:2 AGFzbQEAAAAFBAEAggA= +valid valid binary-leb128.wast:7 AGFzbQEAAAAFBwEAgoCAgAA= +valid valid binary-leb128.wast:12 AGFzbQEAAAAFBgEBggCCAA== +valid valid binary-leb128.wast:18 AGFzbQEAAAAFCQEBggCCgICAAA== +valid valid binary-leb128.wast:24 AGFzbQEAAAAFAwEAAAsHAYAAQQALAA== +valid valid binary-leb128.wast:32 AGFzbQEAAAAEBAFwAAAJCQECgABBAAsAAA== +valid valid binary-leb128.wast:41 AGFzbQEAAAAAigABMTIzNDU2Nzg5 +valid valid binary-leb128.wast:49 AGFzbQEAAAAAC4gAMTIzNDU2Nzg5 +valid valid binary-leb128.wast:57 AGFzbQEAAAABCAFgggB/fgF/ +valid valid binary-leb128.wast:66 AGFzbQEAAAABCAFgAn9+gQB/ +valid valid binary-leb128.wast:75 AGFzbQEAAAABBQFgAX8AAhcBiABzcGVjdGVzdAlwcmludF9pMzIAAA== +valid valid binary-leb128.wast:87 AGFzbQEAAAABBQFgAX8AAhcBCHNwZWN0ZXN0iQBwcmludF9pMzIAAA== +valid valid binary-leb128.wast:99 AGFzbQEAAAABBQFgAX8AAhcBCHNwZWN0ZXN0CXByaW50X2kzMgCAAA== +valid valid binary-leb128.wast:111 AGFzbQEAAAABBAFgAAADAwGAAAoEAQIACw== +valid valid binary-leb128.wast:120 AGFzbQEAAAABBAFgAAADAgEABwcBggBmMQAACgQBAgAL +valid valid binary-leb128.wast:133 AGFzbQEAAAABBAFgAAADAgEABwcBAmYxAIAACgQBAgAL +valid valid binary-leb128.wast:146 AGFzbQEAAAABBAFgAAADAgEACgWBAAIACw== +valid valid binary-leb128.wast:158 AGFzbQEAAAAGBwF/AEGAAAs= +valid valid binary-leb128.wast:165 AGFzbQEAAAAGBwF/AEH/fws= +valid valid binary-leb128.wast:172 AGFzbQEAAAAGCgF/AEGAgICAAAs= +valid valid binary-leb128.wast:179 AGFzbQEAAAAGCgF/AEH/////fws= +valid valid binary-leb128.wast:187 AGFzbQEAAAAGBwF+AEKAAAs= +valid valid binary-leb128.wast:194 AGFzbQEAAAAGBwF+AEL/fws= +valid valid binary-leb128.wast:201 AGFzbQEAAAAGDwF+AEKAgICAgICAgIAACw== +valid valid binary-leb128.wast:208 AGFzbQEAAAAGDwF+AEL///////////9/Cw== +malformed invalid binary-leb128.wast:218 AGFzbQEAAAAFCAEAgoCAgIAA +malformed invalid binary-leb128.wast:226 AGFzbQEAAAAFCgEBggCCgICAgAA= +malformed invalid binary-leb128.wast:235 AGFzbQEAAAAFAwEAAAsLAYCAgICAAEEACwA= +malformed invalid binary-leb128.wast:246 AGFzbQEAAAAEBAFwAAAJCwGAgICAgABBAAsA +malformed invalid binary-leb128.wast:257 AGFzbQEAAAAAg4CAgIAAATEy +malformed invalid binary-leb128.wast:268 AGFzbQEAAAAACoOAgICAADEyMzQ= +malformed invalid binary-leb128.wast:279 AGFzbQEAAAABDAFggoCAgIAAf34Bfw== +malformed invalid binary-leb128.wast:291 AGFzbQEAAAABDAFgAn9+gYCAgIAAfw== +malformed invalid binary-leb128.wast:303 AGFzbQEAAAABBQFgAX8AAhsBiICAgIAAc3BlY3Rlc3QJcHJpbnRfaTMyAAA= +malformed invalid binary-leb128.wast:318 AGFzbQEAAAABBQFgAX8AAhsBCHNwZWN0ZXN0iYCAgIAAcHJpbnRfaTMyAAA= +malformed invalid binary-leb128.wast:333 AGFzbQEAAAABBQFgAX8AAhsBCHNwZWN0ZXN0CXByaW50X2kzMgCAgICAgAA= +malformed invalid binary-leb128.wast:348 AGFzbQEAAAABBAFgAAADAwGAgICAgAAKBAECAAs= +malformed invalid binary-leb128.wast:360 AGFzbQEAAAABBAFgAAADAgEABwsBgoCAgIAAZjEAAAoEAQIACw== +malformed invalid binary-leb128.wast:376 AGFzbQEAAAABBAFgAAADAgEABwsBAmYxAICAgICAAAoEAQIACw== +malformed invalid binary-leb128.wast:392 AGFzbQEAAAABBAFgAAADAgEACgWBgICAgAACAAs= +malformed invalid binary-leb128.wast:405 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAQF/QQAoAoKAgICAgICAgIAAGgs= +malformed invalid binary-leb128.wast:424 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAQF/QQAogoCAgIAAABoL +malformed invalid binary-leb128.wast:443 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEgEQAQF/QQBBAzaCgICAgAADCw== +malformed invalid binary-leb128.wast:462 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEgEQAQF/QQBBAzYCgoCAgICAgICAgAAL +malformed invalid binary-leb128.wast:483 AGFzbQEAAAAGCwF/AEGAgICAgAAL +malformed invalid binary-leb128.wast:493 AGFzbQEAAAAGCwF/AEH//////38L +malformed invalid binary-leb128.wast:504 AGFzbQEAAAAGEAF+AEKAgICAgICAgICAAAs= +malformed invalid binary-leb128.wast:514 AGFzbQEAAAAGEAF+AEL/////////////fws= +malformed invalid binary-leb128.wast:526 AGFzbQEAAAAFBwEAgoCAgHA= +malformed invalid binary-leb128.wast:534 AGFzbQEAAAAFBwEAgoCAgEA= +malformed invalid binary-leb128.wast:542 AGFzbQEAAAAFCQEBggCCgICAEA== +malformed invalid binary-leb128.wast:551 AGFzbQEAAAAFCQEBggCCgICAQA== +malformed invalid binary-leb128.wast:560 AGFzbQEAAAAFAwEAAAsKAYCAgIAQQQALAA== +malformed invalid binary-leb128.wast:571 AGFzbQEAAAAEBAFwAAAJCgGAgICAEEEACwA= +malformed invalid binary-leb128.wast:582 AGFzbQEAAAAAg4CAgBABMTI= +malformed invalid binary-leb128.wast:593 AGFzbQEAAAAACYOAgIBAMTIzNA== +malformed invalid binary-leb128.wast:604 AGFzbQEAAAABCwFggoCAgBB/fgF/ +malformed invalid binary-leb128.wast:616 AGFzbQEAAAABCwFgAn9+gYCAgEB/ +malformed invalid binary-leb128.wast:628 AGFzbQEAAAABBQFgAX8AAhoBiICAgBBzcGVjdGVzdAlwcmludF9pMzIAAA== +malformed invalid binary-leb128.wast:643 AGFzbQEAAAABBQFgAX8AAhoBCHNwZWN0ZXN0iYCAgEBwcmludF9pMzIAAA== +malformed invalid binary-leb128.wast:658 AGFzbQEAAAABBQFgAX8AAhoBCHNwZWN0ZXN0CXByaW50X2kzMgCAgICAEA== +malformed invalid binary-leb128.wast:673 AGFzbQEAAAABBAFgAAADBgGAgICAEAoEAQIACw== +malformed invalid binary-leb128.wast:686 AGFzbQEAAAABBAFgAAADAgEABwoBgoCAgBBmMQAACgQBAgAL +malformed invalid binary-leb128.wast:702 AGFzbQEAAAABBAFgAAADAgEABwoBAmYxAICAgIAQCgQBAgAL +malformed invalid binary-leb128.wast:718 AGFzbQEAAAABBAFgAAADAgEACgiBgICAEAIACw== +malformed invalid binary-leb128.wast:731 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEAEOAQF/QQAoAoKAgICAgICAgBAaCw== +malformed invalid binary-leb128.wast:751 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEAEOAQF/QQAoAoKAgICAgICAgEAaCw== +malformed invalid binary-leb128.wast:771 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEAEOAQF/QQAogoCAgBAAGgs= +malformed invalid binary-leb128.wast:789 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEAEOAQF/QQAogoCAgEAAGgs= +malformed invalid binary-leb128.wast:808 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAQF/QQBBAzaCgICAEAML +malformed invalid binary-leb128.wast:827 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAQF/QQBBAzaCgICAQAML +malformed invalid binary-leb128.wast:846 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAQF/QQBBAzYCgoCAgICAgICAEAs= +malformed invalid binary-leb128.wast:866 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAQF/QQBBAzYCgoCAgICAgICAQAs= +malformed invalid binary-leb128.wast:888 AGFzbQEAAAAGCgF/AEGAgICAcAs= +malformed invalid binary-leb128.wast:898 AGFzbQEAAAAGCgF/AEH/////Dws= +malformed invalid binary-leb128.wast:908 AGFzbQEAAAAGCgF/AEGAgICAHws= +malformed invalid binary-leb128.wast:918 AGFzbQEAAAAGCgF/AEH/////Tws= +malformed invalid binary-leb128.wast:929 AGFzbQEAAAAGDwF+AEKAgICAgICAgIB+Cw== +malformed invalid binary-leb128.wast:939 AGFzbQEAAAAGDwF+AEL///////////8BCw== +malformed invalid binary-leb128.wast:949 AGFzbQEAAAAGDwF+AEKAgICAgICAgIACCw== +malformed invalid binary-leb128.wast:959 AGFzbQEAAAAGDwF+AEL///////////9BCw== +valid valid binary-leb128.wast:969 AGFzbQEAAAABBAFgAAADAgEAChsBGQAA/IAAAPyBgAAA/IaAgAAA/IeAgIAAAAs= +malformed invalid binary-leb128.wast:990 AGFzbQEAAAABBAFgAAADAgEACg0BCwAA/IeAgICAAAAL +valid valid binary-leb128.wast:1015 AGFzbQEAAAAFAwEAAAsIAYIAAEEACwA= +valid valid binary-leb128.wast:1024 AGFzbQEAAAAFAwEAAAsJAYIAgABBAAsA +valid valid binary-leb128.wast:1035 AGFzbQEAAAAEBAFwAAAJBwGAAEEACwA= +valid valid binary-leb128.wast:1052 AGFzbQEAAAAEBAFwAAAJCQGCAABBAAsAAA== +valid valid binary-leb128.wast:1061 AGFzbQEAAAAEBAFwAAAJCgGCAIAAQQALAAA= +malformed invalid binary-leb128.wast:1073 AGFzbQEAAAABBQHgfwAA +valid valid binary.wast:1 AGFzbQEAAAA= +malformed invalid binary.wast:6 +malformed invalid binary.wast:7 AQ== +malformed invalid binary.wast:8 AGFz +malformed invalid binary.wast:9 YXNtAA== +malformed invalid binary.wast:10 bXNhAA== +malformed invalid binary.wast:11 bXNhAAEAAAA= +malformed invalid binary.wast:12 bXNhAAAAAAE= +malformed invalid binary.wast:13 YXNtAQAAAAA= +malformed invalid binary.wast:14 d2FzbQEAAAA= +malformed invalid binary.wast:15 f2FzbQEAAAA= +malformed invalid binary.wast:16 gGFzbQEAAAA= +malformed invalid binary.wast:17 gmFzbQEAAAA= +malformed invalid binary.wast:18 /2FzbQEAAAA= +malformed invalid binary.wast:21 AAAAAW1zYQA= +malformed invalid binary.wast:24 YQBtcwABAAA= +malformed invalid binary.wast:25 c20AYQAAAQA= +malformed invalid binary.wast:28 AEFTTQEAAAA= +malformed invalid binary.wast:31 AIGilAEAAAA= +malformed invalid binary.wast:34 77u/AGFzbQEAAAA= +malformed invalid binary.wast:37 AGFzbQ== +malformed invalid binary.wast:38 AGFzbQE= +malformed invalid binary.wast:39 AGFzbQEAAA== +malformed invalid binary.wast:40 AGFzbQAAAAA= +malformed invalid binary.wast:41 AGFzbQ0AAAA= +malformed invalid binary.wast:42 AGFzbQ4AAAA= +malformed invalid binary.wast:43 AGFzbQABAAA= +malformed invalid binary.wast:44 AGFzbQAAAQA= +malformed invalid binary.wast:45 AGFzbQAAAAE= +malformed invalid binary.wast:48 AGFzbQEAAAAOAQA= +malformed invalid binary.wast:49 AGFzbQEAAAB/AQA= +malformed invalid binary.wast:50 AGFzbQEAAACAAQABAQA= +malformed invalid binary.wast:51 AGFzbQEAAACBAQABAQA= +malformed invalid binary.wast:52 AGFzbQEAAAD/AQABAQA= +malformed invalid binary.wast:56 AGFzbQEAAAABBAFgAAADAwIAAAoMAgQAQQEaBQBBARoL +malformed invalid binary.wast:77 AGFzbQEAAAABBAFgAAADAgEACgYBBABBARo= +malformed invalid binary.wast:93 AGFzbQEAAAABBAFgAAADAgEACgYBBABBARoLAwEBAA== +malformed invalid binary.wast:113 AGFzbQEAAAABBAFgAAADAgEABgUBfwBBAAoEAQIACw== +malformed invalid binary.wast:126 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCQEHAEEAQAEaCw== +malformed valid binary.wast:146 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAQIAAGgs= +malformed valid binary.wast:166 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQICAABoL +malformed valid binary.wast:185 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDAEKAEEAQICAgAAaCw== +malformed valid binary.wast:204 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDQELAEEAQICAgIAAGgs= +malformed invalid binary.wast:224 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKBwEFAD8BGgs= +malformed valid binary.wast:243 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCAEGAD+AABoL +malformed valid binary.wast:262 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCQEHAD+AgAAaCw== +malformed valid binary.wast:280 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAD+AgIAAGgs= +malformed valid binary.wast:298 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAD+AgICAABoL +malformed invalid binary.wast:317 AGFzbQEAAAABBAFgAAADAgEACgwBCgKAgICAEH8Cfgs= +malformed invalid binary.wast:351 AGFzbQEAAAABBAFgAAADAgEACgwBCgL/////D38Cfgs= +malformed invalid binary.wast:367 AGFzbQEAAAABBgFgAn9/AAMCAQAKHAEaBICAgIAEf4CAgIAEfoCAgIAEfYCAgIAEfAs= +valid valid binary.wast:385 AGFzbQEAAAABBAFgAAADAgEACgoBCAMAfwB+An0L +malformed invalid binary.wast:401 AGFzbQEAAAABBAFgAAADAwIAAA== +malformed invalid binary.wast:411 AGFzbQEAAAAKBAECAAs= +malformed invalid binary.wast:420 AGFzbQEAAAABBAFgAAADAwIAAAoEAQIACw== +malformed invalid binary.wast:431 AGFzbQEAAAABBAFgAAADAgEACgcCAgALAgAL +valid valid binary.wast:441 AGFzbQEAAAADAQA= +valid valid binary.wast:447 AGFzbQEAAAAKAQA= +malformed invalid binary.wast:454 AGFzbQEAAAAMAQMLBQIBAAEA +malformed invalid binary.wast:466 AGFzbQEAAAAMAQELBQIBAAEA +malformed invalid binary.wast:478 AGFzbQEAAAAFAwEAAQwBAQ== +valid valid binary.wast:487 AGFzbQEAAAAMAQA= +malformed invalid binary.wast:494 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDgEMAEEAQQBBAPwIAAALCwMBAQA= +malformed invalid binary.wast:517 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKBwEFAPwJAAsLAwEBAA== +malformed invalid binary.wast:537 AGFzbQEAAAABBAFgAAADAgEABAQBcAAABQMBAAAJBwEFcAHzAAsKBAECAAs= +malformed invalid binary.wast:565 AGFzbQEAAAABBAFgAAADAgEABAQBcAAABQMBAAAJBwEFfwHSAAsKBAECAAs= +valid valid binary.wast:592 AGFzbQEAAAABBAFgAAADAgEABAQBcAAABQMBAAAJBwEFcAHSAAsKBAECAAs= +valid valid binary.wast:617 AGFzbQEAAAABBAFgAAADAgEABAQBcAAABQMBAAAJBwEFcAHQcAsKBAECAAs= +valid valid binary.wast:643 AGFzbQEAAAABAQA= +malformed invalid binary.wast:650 AGFzbQEAAAABBwJgAAA= +malformed invalid binary.wast:661 AGFzbQEAAAABBwFgAABgAAA= +valid valid binary.wast:671 AGFzbQEAAAABBQFgAX8AAgEA +malformed invalid binary.wast:680 AGFzbQEAAAACBAEAAAQ= +malformed invalid binary.wast:690 AGFzbQEAAAACBQEAAAQA +malformed invalid binary.wast:701 AGFzbQEAAAACBAEAAAU= +malformed invalid binary.wast:711 AGFzbQEAAAACBQEAAAUA +malformed invalid binary.wast:722 AGFzbQEAAAACBAEAAIA= +malformed invalid binary.wast:732 AGFzbQEAAAACBQEAAIAA +malformed invalid binary.wast:745 AGFzbQEAAAABBQFgAX8AAhYCCHNwZWN0ZXN0CXByaW50X2kzMgAA +malformed invalid binary.wast:764 AGFzbQEAAAABCQJgAX8AYAF9AAIrAQhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9mMzIAAQ== +valid valid binary.wast:788 AGFzbQEAAAAEAQA= +malformed invalid binary.wast:795 AGFzbQEAAAAEAQE= +malformed invalid binary.wast:805 AGFzbQEAAAAEAwFwAg== +malformed invalid binary.wast:814 AGFzbQEAAAAEBAFwCAA= +malformed invalid binary.wast:824 AGFzbQEAAAAEBgFwgQAAAA== +valid valid binary.wast:835 AGFzbQEAAAAFAQA= +malformed invalid binary.wast:842 AGFzbQEAAAAFAQE= +malformed invalid binary.wast:852 AGFzbQEAAAAFAgEC +malformed invalid binary.wast:860 AGFzbQEAAAAFAwEQAA== +malformed invalid binary.wast:869 AGFzbQEAAAAFBQGBAAAA +malformed invalid binary.wast:878 AGFzbQEAAAAFBQGBAQAA +valid valid binary.wast:888 AGFzbQEAAAAGAQA= +malformed invalid binary.wast:895 AGFzbQEAAAAGBgJ/AEEACw== +malformed invalid binary.wast:906 AGFzbQEAAAAGCwF/AEEAC38AQQAL +valid valid binary.wast:916 AGFzbQEAAAABBAFgAAADAwIAAAcBAAoHAgIACwIACw== +malformed invalid binary.wast:929 AGFzbQEAAAABBAFgAAADAwIAAAcGAgJmMQAACgcCAgALAgAL +malformed invalid binary.wast:950 AGFzbQEAAAABBAFgAAADAwIAAAcLAQJmMQAAAmYyAAEKBwICAAsCAAs= +valid valid binary.wast:970 AGFzbQEAAAABBAFgAAADAgEABAQBcAABCQEACgQBAgAL +malformed invalid binary.wast:984 AGFzbQEAAAABBAFgAAADAgEABAQBcAABCQcCAEEACwEA +malformed invalid binary.wast:1000 AGFzbQEAAAABBAFgAAADAgEABAQBcAABCQoCAEEACwEAAEEA +malformed invalid binary.wast:1017 AGFzbQEAAAABBAFgAAADAgEABAQBcAABCQ0BAEEACwEAAEEACwEACgQBAgAL +valid valid binary.wast:1034 AGFzbQEAAAAFAwEAAQsBAA== +malformed invalid binary.wast:1043 AGFzbQEAAAAFAwEAAQsHAgBBAAsBYQ== +malformed invalid binary.wast:1056 AGFzbQEAAAAFAwEAAQsNAQBBAAsBYQBBAQsBYg== +malformed invalid binary.wast:1069 AGFzbQEAAAAFAwEAAQsMAQBBAwsHYWJjZGVm +malformed invalid binary.wast:1083 AGFzbQEAAAAFAwEAAQsMAQBBAAsFYWJjZGVm +valid valid binary.wast:1096 AGFzbQEAAAABBAFgAAADAgEAChEBDwACQEEBBEBBAQ4AAgsLCw== +malformed invalid binary.wast:1114 AGFzbQEAAAABJQxgAABgAABgAABgAABgAABgAABgAABgAABgAABgAABgAABgAAADAgEAChMBEQACQEEBBEBBAQ4BAAECCwsL +valid valid binary.wast:1147 AGFzbQEAAAABBAFgAAADAgEACAEACgQBAgAL +malformed invalid binary.wast:1161 AGFzbQEAAAABBAFgAAADAgEACAEACAEACgQBAgAL +malformed invalid binary.wast:1178 AGFzbQEAAAABBAFgAAADAgEAAwIBAAoHAgIACwIACw== +malformed invalid binary.wast:1190 AGFzbQEAAAABBAFgAAADAwIAAAoEAQIACwoEAQIACw== +malformed invalid binary.wast:1202 AGFzbQEAAAAMAQEMAQE= +malformed invalid binary.wast:1212 AGFzbQEAAAALAQALAQA= +malformed invalid binary.wast:1222 AGFzbQEAAAAGAQAGAQA= +malformed invalid binary.wast:1232 AGFzbQEAAAAHAQAHAQA= +malformed invalid binary.wast:1242 AGFzbQEAAAAEAQAEAQA= +malformed invalid binary.wast:1252 AGFzbQEAAAAJAQAJAQA= +malformed invalid binary.wast:1262 AGFzbQEAAAACAQACAQA= +malformed invalid binary.wast:1272 AGFzbQEAAAABAQABAQA= +malformed invalid binary.wast:1282 AGFzbQEAAAAFAQAFAQA= +malformed invalid binary.wast:1292 AGFzbQEAAAACAQABAQA= +malformed invalid binary.wast:1302 AGFzbQEAAAADAQACAQA= +malformed invalid binary.wast:1312 AGFzbQEAAAAEAQADAQA= +malformed invalid binary.wast:1322 AGFzbQEAAAAFAQAEAQA= +malformed invalid binary.wast:1332 AGFzbQEAAAAGAQAFAQA= +malformed invalid binary.wast:1342 AGFzbQEAAAAHAQAGAQA= +malformed invalid binary.wast:1352 AGFzbQEAAAABBAFgAAADAgEACAEABwEA +malformed invalid binary.wast:1364 AGFzbQEAAAABBAFgAAADAgEACQEACAEA +malformed invalid binary.wast:1376 AGFzbQEAAAAMAQEJAQA= +malformed invalid binary.wast:1386 AGFzbQEAAAAKAQAMAQE= +malformed invalid binary.wast:1396 AGFzbQEAAAALAQAKAQA= +valid valid block.wast:3 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 +invalid invalid block.wast:497 AGFzbQEAAAABBAFgAAADAgEACgkBBwACQEEACws= +invalid invalid block.wast:505 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAAkALCw== +invalid invalid block.wast:509 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAAkALCw== +invalid invalid block.wast:513 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAAkALCw== +invalid invalid block.wast:517 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAAkALCw== +invalid invalid block.wast:522 AGFzbQEAAAABBAFgAAADAgEACgkBBwACQEEBCws= +invalid invalid block.wast:528 AGFzbQEAAAABBAFgAAADAgEACgkBBwACQEIBCws= +invalid invalid block.wast:534 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEMAAIA/Cws= +invalid invalid block.wast:540 AGFzbQEAAAABBAFgAAADAgEAChABDgACQEQAAAAAAADwPwsL +invalid invalid block.wast:546 AGFzbQEAAAABBAFgAAADAgEACgsBCQACQEEBQQILCw== +invalid invalid block.wast:552 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAAn8LCw== +invalid invalid block.wast:558 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAAn4LCw== +invalid invalid block.wast:564 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAAn0LCw== +invalid invalid block.wast:570 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAAnwLCw== +invalid invalid block.wast:576 AGFzbQEAAAABBgFgAAJ/fwMCAQAKBwEFAAIACws= +invalid invalid block.wast:583 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAAJAAn8LGgsL +invalid invalid block.wast:592 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAANAAn8LGgsL +invalid invalid block.wast:601 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEABEACfwsaCws= +invalid invalid block.wast:611 AGFzbQEAAAABBQFgAAF/AwIBAAoIAQYAAn8BCws= +invalid invalid block.wast:617 AGFzbQEAAAABBQFgAAF+AwIBAAoIAQYAAn4BCws= +invalid invalid block.wast:623 AGFzbQEAAAABBQFgAAF9AwIBAAoIAQYAAn0BCws= +invalid invalid block.wast:629 AGFzbQEAAAABBQFgAAF8AwIBAAoIAQYAAnwBCws= +invalid invalid block.wast:635 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCAEGAAIAAQsL +invalid invalid block.wast:641 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcAAn9CAAsL +invalid invalid block.wast:647 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAAn9DAAAAAAsL +invalid invalid block.wast:653 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn9EAAAAAAAAAAALCw== +invalid invalid block.wast:659 AGFzbQEAAAABBQFgAAF+AwIBAAoJAQcAAn5BAAsL +invalid invalid block.wast:665 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAAn5DAAAAAAsL +invalid invalid block.wast:671 AGFzbQEAAAABBQFgAAF+AwIBAAoQAQ4AAn5EAAAAAAAAAAALCw== +invalid invalid block.wast:677 AGFzbQEAAAABBQFgAAF9AwIBAAoJAQcAAn1BAAsL +invalid invalid block.wast:683 AGFzbQEAAAABBQFgAAF9AwIBAAoJAQcAAn1CAAsL +invalid invalid block.wast:689 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AAn1EAAAAAAAAAAALCw== +invalid invalid block.wast:695 AGFzbQEAAAABBQFgAAF8AwIBAAoJAQcAAnxBAAsL +invalid invalid block.wast:701 AGFzbQEAAAABBQFgAAF8AwIBAAoJAQcAAnxCAAsL +invalid invalid block.wast:707 AGFzbQEAAAABBQFgAAF9AwIBAAoMAQoAAnxDAAAAAAsL +invalid invalid block.wast:713 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCQEHAAIAQQALCw== +invalid invalid block.wast:719 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCwEJAEEBAgBBAgsL +invalid invalid block.wast:725 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAn9BAUECCws= +invalid invalid block.wast:732 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAn4AAAAbCws= +invalid invalid block.wast:738 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAn0AAAAbCws= +invalid invalid block.wast:744 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAnwAAAAbCws= +invalid invalid block.wast:750 AGFzbQEAAAABBQFgAAF+AwIBAAoLAQkAAn8AAAAbCws= +invalid invalid block.wast:756 AGFzbQEAAAABBQFgAAF+AwIBAAoLAQkAAn0AAAAbCws= +invalid invalid block.wast:762 AGFzbQEAAAABBQFgAAF+AwIBAAoLAQkAAnwAAAAbCws= +invalid invalid block.wast:768 AGFzbQEAAAABBQFgAAF9AwIBAAoLAQkAAn8AAAAbCws= +invalid invalid block.wast:774 AGFzbQEAAAABBQFgAAF9AwIBAAoLAQkAAn4AAAAbCws= +invalid invalid block.wast:780 AGFzbQEAAAABBQFgAAF9AwIBAAoLAQkAAnwAAAAbCws= +invalid invalid block.wast:786 AGFzbQEAAAABBQFgAAF8AwIBAAoLAQkAAn8AAAAbCws= +invalid invalid block.wast:792 AGFzbQEAAAABBQFgAAF8AwIBAAoLAQkAAn4AAAAbCws= +invalid invalid block.wast:798 AGFzbQEAAAABBQFgAAF8AwIBAAoLAQkAAn0AAAAbCws= +invalid invalid block.wast:805 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcAAn8MAAsL +invalid invalid block.wast:811 AGFzbQEAAAABBQFgAAF+AwIBAAoJAQcAAn4MAAsL +invalid invalid block.wast:817 AGFzbQEAAAABBQFgAAF9AwIBAAoJAQcAAn0MAAsL +invalid invalid block.wast:823 AGFzbQEAAAABBQFgAAF8AwIBAAoJAQcAAnwMAAsL +invalid invalid block.wast:829 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCQEHAAIADAALCw== +invalid invalid block.wast:836 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAn8MAEEBCws= +invalid invalid block.wast:842 AGFzbQEAAAABBQFgAAF+AwIBAAoLAQkAAn4MAEIBCws= +invalid invalid block.wast:848 AGFzbQEAAAABBQFgAAF9AwIBAAoOAQwAAn0MAEMAAIA/Cws= +invalid invalid block.wast:854 AGFzbQEAAAABBQFgAAF8AwIBAAoSARAAAnwMAEQAAAAAAADwPwsL +invalid invalid block.wast:860 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDQELAAIADABBAUECCws= +invalid invalid block.wast:867 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAAn8BDABBAQsL +invalid invalid block.wast:873 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAAn4BDABCAQsL +invalid invalid block.wast:879 AGFzbQEAAAABBQFgAAF9AwIBAAoPAQ0AAn0BDABDAACAPwsL +invalid invalid block.wast:885 AGFzbQEAAAABBQFgAAF8AwIBAAoTAREAAnwBDABEAAAAAAAA8D8LCw== +invalid invalid block.wast:892 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAAn9CAQwAQQELCw== +invalid invalid block.wast:898 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn9DAACAPwwAQQELCw== +invalid invalid block.wast:904 AGFzbQEAAAABBQFgAAF/AwIBAAoUARIAAn9EAAAAAAAA8D8MAEEBCws= +invalid invalid block.wast:910 AGFzbQEAAAABBQFgAAF+AwIBAAoNAQsAAn5BAQwAQgELCw== +invalid invalid block.wast:916 AGFzbQEAAAABBQFgAAF+AwIBAAoQAQ4AAn5DAACAPwwAQgELCw== +invalid invalid block.wast:922 AGFzbQEAAAABBQFgAAF+AwIBAAoUARIAAn5EAAAAAAAA8D8MAEIBCws= +invalid invalid block.wast:928 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AAn1BAQwAQwAAgD8LCw== +invalid invalid block.wast:934 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AAn1CAQwAQwAAgD8LCw== +invalid invalid block.wast:940 AGFzbQEAAAABBQFgAAF9AwIBAAoXARUAAn1EAAAAAAAA8D8MAEMAAIA/Cws= +invalid invalid block.wast:946 AGFzbQEAAAABBQFgAAF8AwIBAAoUARIAAn5BAQwARAAAAAAAAPA/Cws= +invalid invalid block.wast:952 AGFzbQEAAAABBQFgAAF8AwIBAAoUARIAAnxCAQwARAAAAAAAAPA/Cws= +invalid invalid block.wast:958 AGFzbQEAAAABBQFgAAF8AwIBAAoXARUAAnxDAACAPwwARAAAAAAAAPA/Cws= +invalid invalid block.wast:964 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDwENAAIAQQAMAEEBQQILCw== +invalid invalid block.wast:970 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDwENAEEBAgBBAAwAQQILCw== +invalid invalid block.wast:977 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAn8BDABBAQwACws= +invalid invalid block.wast:983 AGFzbQEAAAABBQFgAAF+AwIBAAoOAQwAAn4BDABCAQwACws= +invalid invalid block.wast:989 AGFzbQEAAAABBQFgAAF9AwIBAAoRAQ8AAn0BDABDAACAPwwACws= +invalid invalid block.wast:995 AGFzbQEAAAABBQFgAAF8AwIBAAoVARMAAnwBDABEAAAAAAAA8D8MAAsL +invalid invalid block.wast:1001 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEAEOAAIAAQwAQQFBAgwACws= +invalid invalid block.wast:1008 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAn9CAQwAQQEMAAsL +invalid invalid block.wast:1014 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAAn9DAACAPwwAQQEMAAsL +invalid invalid block.wast:1020 AGFzbQEAAAABBQFgAAF/AwIBAAoWARQAAn9EAAAAAAAA8D8MAEEBDAALCw== +invalid invalid block.wast:1026 AGFzbQEAAAABBQFgAAF+AwIBAAoPAQ0AAn5BAQwAQgEMAAsL +invalid invalid block.wast:1032 AGFzbQEAAAABBQFgAAF+AwIBAAoSARAAAn5DAACAPwwAQgEMAAsL +invalid invalid block.wast:1038 AGFzbQEAAAABBQFgAAF+AwIBAAoWARQAAn5EAAAAAAAA8D8MAEIBDAALCw== +invalid invalid block.wast:1044 AGFzbQEAAAABBQFgAAF9AwIBAAoSARAAAn1BAQwAQwAAgD8MAAsL +invalid invalid block.wast:1050 AGFzbQEAAAABBQFgAAF9AwIBAAoSARAAAn1CAQwAQwAAgD8MAAsL +invalid invalid block.wast:1056 AGFzbQEAAAABBQFgAAF9AwIBAAoZARcAAn1EAAAAAAAA8D8MAEMAAIA/DAALCw== +invalid invalid block.wast:1062 AGFzbQEAAAABBQFgAAF8AwIBAAoWARQAAnxBAQwARAAAAAAAAPA/DAALCw== +invalid invalid block.wast:1068 AGFzbQEAAAABBQFgAAF8AwIBAAoWARQAAnxCAQwARAAAAAAAAPA/DAALCw== +invalid invalid block.wast:1074 AGFzbQEAAAABBQFgAAF8AwIBAAoZARcAAnxDAACAPwwARAAAAAAAAPA/DAALCw== +invalid invalid block.wast:1080 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEQEPAAIAQQAMAEEBQQIMAAsL +invalid invalid block.wast:1087 AGFzbQEAAAABBAFgAAADAgEAChABDgACfwJ/QQEMAQsMAAsL +invalid invalid block.wast:1093 AGFzbQEAAAABBAFgAAADAgEAChABDgACfgJ+QgEMAQsMAAsL +invalid invalid block.wast:1099 AGFzbQEAAAABBAFgAAADAgEAChMBEQACfQJ9QwAAgD8MAQsMAAsL +invalid invalid block.wast:1105 AGFzbQEAAAABBAFgAAADAgEAChcBFQACfAJ8RAAAAAAAAPA/DAELDAALCw== +invalid invalid block.wast:1111 AGFzbQEAAAABCQJgAABgAAJ/fwMCAQAKEgEQAAIBAgFBAUECDAELDAALCw== +invalid invalid block.wast:1118 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn8CQAwBC0EBDAALCw== +invalid invalid block.wast:1124 AGFzbQEAAAABBQFgAAF+AwIBAAoQAQ4AAn4CQAwBC0IBDAALCw== +invalid invalid block.wast:1130 AGFzbQEAAAABBQFgAAF9AwIBAAoTAREAAn0CQAwBC0MAAIA/DAALCw== +invalid invalid block.wast:1136 AGFzbQEAAAABBQFgAAF8AwIBAAoXARUAAnwCQAwBC0QAAAAAAADwPwwACws= +invalid invalid block.wast:1142 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEgEQAAIAAkAMAQtBAUECDAALCw== +invalid invalid block.wast:1149 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAn8CfwEMAQtBAQwACws= +invalid invalid block.wast:1155 AGFzbQEAAAABBQFgAAF+AwIBAAoRAQ8AAn4CfgEMAQtCAQwACws= +invalid invalid block.wast:1161 AGFzbQEAAAABBQFgAAF9AwIBAAoUARIAAn0CfQEMAQtDAACAPwwACws= +invalid invalid block.wast:1167 AGFzbQEAAAABBQFgAAF8AwIBAAoYARYAAnwCfAEMAQtEAAAAAAAA8D8MAAsL +invalid invalid block.wast:1173 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEwERAAIAAgABDAELQQFBAgwACws= +invalid invalid block.wast:1180 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAAn8Cf0IBDAELQQEMAAsL +invalid invalid block.wast:1188 AGFzbQEAAAABBQFgAAF/AwIBAAoVARMAAn8Cf0MAAIA/DAELQQEMAAsL +invalid invalid block.wast:1196 AGFzbQEAAAABBQFgAAF/AwIBAAoZARcAAn8Cf0QAAAAAAADwPwwBC0EBDAALCw== +invalid invalid block.wast:1204 AGFzbQEAAAABBQFgAAF+AwIBAAoSARAAAn4CfkEBDAELQgEMAAsL +invalid invalid block.wast:1212 AGFzbQEAAAABBQFgAAF+AwIBAAoVARMAAn4CfkMAAIA/DAELQgEMAAsL +invalid invalid block.wast:1220 AGFzbQEAAAABBQFgAAF+AwIBAAoZARcAAn4CfkQAAAAAAADwPwwBC0IBDAALCw== +invalid invalid block.wast:1228 AGFzbQEAAAABBQFgAAF9AwIBAAoVARMAAn0CfUEBDAELQwAAgD8MAAsL +invalid invalid block.wast:1236 AGFzbQEAAAABBQFgAAF9AwIBAAoVARMAAn0CfUIBDAELQwAAgD8MAAsL +invalid invalid block.wast:1244 AGFzbQEAAAABBQFgAAF9AwIBAAocARoAAn0CfUQAAAAAAADwPwwBC0MAAIA/DAALCw== +invalid invalid block.wast:1252 AGFzbQEAAAABBQFgAAF8AwIBAAoZARcAAnwCfEEBDAELRAAAAAAAAPA/DAALCw== +invalid invalid block.wast:1260 AGFzbQEAAAABBQFgAAF8AwIBAAoZARcAAnwCfEIBDAELRAAAAAAAAPA/DAALCw== +invalid invalid block.wast:1268 AGFzbQEAAAABBQFgAAF8AwIBAAocARoAAnwCfEMAAIA/DAELRAAAAAAAAPA/DAALCw== +invalid invalid block.wast:1276 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFAESAAIAAgBBAAwBC0EBQQIMAAsL +invalid invalid block.wast:1285 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAAkAMAAtoCw== +invalid invalid block.wast:1291 AGFzbQEAAAABBQFgAAF+AwIBAAoKAQgAAkAMAAt6Cw== +invalid invalid block.wast:1297 AGFzbQEAAAABBQFgAAF9AwIBAAoKAQgAAkAMAAuOCw== +invalid invalid block.wast:1303 AGFzbQEAAAABBQFgAAF8AwIBAAoKAQgAAkAMAAucCw== +invalid invalid block.wast:1309 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAAkAMAAtqCw== +invalid invalid block.wast:1316 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAkABDAALaAs= +invalid invalid block.wast:1322 AGFzbQEAAAABBQFgAAF+AwIBAAoLAQkAAkABDAALegs= +invalid invalid block.wast:1328 AGFzbQEAAAABBQFgAAF9AwIBAAoLAQkAAkABDAALjgs= +invalid invalid block.wast:1334 AGFzbQEAAAABBQFgAAF8AwIBAAoLAQkAAkABDAALnAs= +invalid invalid block.wast:1340 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAkABDAALags= +invalid invalid block.wast:1347 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAAkBCCQwAC3oL +invalid invalid block.wast:1353 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAkBDAAAQQQwAC44L +invalid invalid block.wast:1359 AGFzbQEAAAABBQFgAAF/AwIBAAoTAREAAkBEAAAAAAAAIkAMAAucCw== +invalid invalid block.wast:1365 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAAkBBCQwAC2gL +invalid invalid block.wast:1371 AGFzbQEAAAABBQFgAAF+AwIBAAoPAQ0AAkBDAAAQQQwAC44L +invalid invalid block.wast:1377 AGFzbQEAAAABBQFgAAF+AwIBAAoTAREAAkBEAAAAAAAAIkAMAAucCw== +invalid invalid block.wast:1383 AGFzbQEAAAABBQFgAAF9AwIBAAoMAQoAAkBBCQwAC2gL +invalid invalid block.wast:1389 AGFzbQEAAAABBQFgAAF9AwIBAAoMAQoAAkBCCQwAC3oL +invalid invalid block.wast:1395 AGFzbQEAAAABBQFgAAF9AwIBAAoTAREAAkBEAAAAAAAAIkAMAAucCw== +invalid invalid block.wast:1401 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAAkBBCQwAC2gL +invalid invalid block.wast:1407 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAAkBCCQwAC3oL +invalid invalid block.wast:1413 AGFzbQEAAAABBQFgAAF8AwIBAAoPAQ0AAkBDAAAQQQwAC44L +invalid invalid block.wast:1419 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAkBCCUEKDAALags= +invalid invalid block.wast:1426 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoIAQYAAgEaCws= +invalid invalid block.wast:1432 AGFzbQEAAAABCQJgAABgAn98AAMCAQAKCQEHAAIBGhoLCw== +invalid invalid block.wast:1438 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoNAQsAQwAAAAACARoLCw== +invalid invalid block.wast:1444 AGFzbQEAAAABCQJgAABgAn1/AAMCAQAKDgEMAEMAAAAAAgEaGgsL +invalid invalid block.wast:1450 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoLAQkAAkACARoLCws= +invalid invalid block.wast:1456 AGFzbQEAAAABCQJgAABgAn98AAMCAQAKDAEKAAJAAgEaGgsLCw== +invalid invalid block.wast:1462 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoQAQ4AAkBDAAAAAAIBGgsLCw== +invalid invalid block.wast:1468 AGFzbQEAAAABCQJgAABgAn1/AAMCAQAKEQEPAAJAQwAAAAACARoaCwsL +valid valid br.wast:3 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= +invalid invalid br.wast:484 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAn9BAAJADAELCws= +invalid invalid br.wast:497 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAAJ/DAAMAAtFGgs= +invalid invalid br.wast:506 AGFzbQEAAAABBAFgAAADAgEAChEBDwBBAAJ/DABBAQ0AC0UaCw== +invalid invalid br.wast:515 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAAJ/DAAOAAALRRoL +invalid invalid br.wast:524 AGFzbQEAAAABBAFgAAADAgEACgwBCgACfwwADwtFGgs= +invalid invalid br.wast:535 AGFzbQEAAAABBAFgAAADAgEAChABDgACfwwAQQFBAhsLRRoL +invalid invalid br.wast:546 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABChICCwACfwwAEAELRRoLBAAgAAs= +invalid invalid br.wast:558 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoVAgQAIAALDgACfwwAQQARAAALRRoL +invalid invalid br.wast:574 AGFzbQEAAAABBAFgAAADAgEAChEBDwEBfwJ/DAAhACAAC0UaCw== +invalid invalid br.wast:586 AGFzbQEAAAABBAFgAAADAgEACg8BDQEBfwJ/DAAiAAtFGgs= +invalid invalid br.wast:598 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKDwENAAJ/DAAkACMAC0UaCw== +invalid invalid br.wast:610 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDQELAAJ/DABAAAtFGgs= +invalid invalid br.wast:622 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAAJ/DAAoAgALRRoL +invalid invalid br.wast:634 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEAEOAAJ/DABBADYCAAtFGgs= +invalid invalid br.wast:647 AGFzbQEAAAABBAFgAAADAgEACgYBBAAMAQs= +invalid invalid br.wast:651 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQAJADAULCws= +invalid invalid br.wast:655 AGFzbQEAAAABBAFgAAADAgEACgoBCAAMgYCAgAEL +valid valid br_if.wast:3 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== +invalid invalid br_if.wast:481 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEEADQBoCws= +invalid invalid br_if.wast:485 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEEADQB6Cws= +invalid invalid br_if.wast:489 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEEADQCMCws= +invalid invalid br_if.wast:493 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEEADQCaCws= +invalid invalid br_if.wast:498 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEEBDQBoCws= +invalid invalid br_if.wast:502 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEIBDQB6Cws= +invalid invalid br_if.wast:506 AGFzbQEAAAABBAFgAAADAgEACg8BDQACQEMAAIA/DQCMCws= +invalid invalid br_if.wast:510 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEIBDQCaCws= +invalid invalid br_if.wast:515 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAAn9BAA0AQQELCw== +invalid invalid br_if.wast:521 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAAn9BAQ0AQQELCw== +invalid invalid br_if.wast:527 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEAQQANAAsL +invalid invalid br_if.wast:533 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEAQQENAAsL +invalid invalid br_if.wast:540 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAn8BQQANAEEBCws= +invalid invalid br_if.wast:546 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAn8BQQENAEEBCws= +invalid invalid br_if.wast:552 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn9CAUEADQAaQQELCw== +invalid invalid br_if.wast:569 AGFzbQEAAAABBAFgAAADAgEACgkBBwACQA0ACws= +invalid invalid br_if.wast:575 AGFzbQEAAAABBAFgAAADAgEACgoBCAACQAENAAsL +invalid invalid br_if.wast:581 AGFzbQEAAAABBAFgAAADAgEACgsBCQACQEIADQALCw== +invalid invalid br_if.wast:587 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAn9BAAENAEEBCws= +invalid invalid br_if.wast:593 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn9BAAJAQQENAQsLCw== +invalid invalid br_if.wast:599 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAn9BAEIADQBBAQsL +invalid invalid br_if.wast:606 AGFzbQEAAAABBAFgAAADAgEAChIBEAACQEEAQQAEfw0ACwtFGgs= +invalid invalid br_if.wast:618 AGFzbQEAAAABBAFgAAADAgEAChQBEgACQEEAQQAEf0EBDQALC0UaCw== +invalid invalid br_if.wast:630 AGFzbQEAAAABBAFgAAADAgEACgwBCgACfw0ADwtFGgs= +invalid invalid br_if.wast:641 AGFzbQEAAAABBAFgAAADAgEACg4BDAACf0EBDQAPC0UaCw== +invalid invalid br_if.wast:654 AGFzbQEAAAABBAFgAAADAgEACggBBgBBAQ0BCw== +invalid invalid br_if.wast:658 AGFzbQEAAAABBAFgAAADAgEACg4BDAACQAJAQQENBQsLCw== +invalid invalid br_if.wast:662 AGFzbQEAAAABBAFgAAADAgEACgwBCgBBAQ2BgICAAQs= +valid valid br_table.wast:3 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 +invalid invalid br_table.wast:1193 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAkBBAQ4AAEEBCws= +invalid invalid br_table.wast:1200 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAAkAOAABBAQsL +invalid invalid br_table.wast:1207 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAn8BQQEOAABBAQsL +invalid invalid br_table.wast:1213 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAAn9CAUEBDgIAAABBAQsL +invalid invalid br_table.wast:1221 AGFzbQEAAAABBAFgAAADAgEAChYBFAACQAJ9QwAAAABBAA4BAAELGgsL +invalid invalid br_table.wast:1232 AGFzbQEAAAABBAFgAAADAgEAChIBEAACfwJ+QQBBAA4BAAELCws= +invalid invalid br_table.wast:1243 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQAEOAgAAAAsL +invalid invalid br_table.wast:1249 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEIADgAACws= +invalid invalid br_table.wast:1255 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn9BAAEOAQAAQQELCw== +invalid invalid br_table.wast:1261 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAn9BAAJAQQAOAAELCws= +invalid invalid br_table.wast:1267 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAn9BAEIADgEAAEEBCws= +invalid invalid br_table.wast:1283 AGFzbQEAAAABBAFgAAADAgEAChMBEQACQEEAQQAEfw4AAAsLRRoL +invalid invalid br_table.wast:1295 AGFzbQEAAAABBAFgAAADAgEAChUBEwACQEEAQQAEf0EBDgAACwtFGgs= +invalid invalid br_table.wast:1307 AGFzbQEAAAABBAFgAAADAgEACg0BCwACfw4AAA8LRRoL +invalid invalid br_table.wast:1318 AGFzbQEAAAABBAFgAAADAgEACg8BDQACf0EBDgAADwtFGgs= +invalid invalid br_table.wast:1330 AGFzbQEAAAABBgFgAX8BfwMCAQAKEgEQAAN/An9BASAADgEAAQsLCw== +invalid invalid br_table.wast:1342 AGFzbQEAAAABBgFgAX8BfwMCAQAKEgEQAAJ/A39BASAADgEAAQsLCw== +invalid invalid br_table.wast:1356 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEBDgECAQsL +invalid invalid br_table.wast:1362 AGFzbQEAAAABBAFgAAADAgEAChABDgACQAJAQQEOAQAFCwsL +invalid invalid br_table.wast:1368 AGFzbQEAAAABBAFgAAADAgEAChIBEAACQEEBDgIAgYCAgAEACws= +invalid invalid br_table.wast:1375 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEBDgEBAgsL +invalid invalid br_table.wast:1387 AGFzbQEAAAABBAFgAAADAgEAChIBEAACQEEBDgIAAIGAgIABCws= +valid valid bulk.wast:2 AGFzbQEAAAAFAwEAAQsGAQEDZm9v +valid valid bulk.wast:6 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAAAwkNAQVwA9IAC9BwC9IBCwoHAgIACwIACw== +valid valid bulk.wast:13 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHEgIEZmlsbAAAB2xvYWQ4X3UAAQoVAgsAIAAgASAC/AsACwcAIAAtAAAL +valid valid bulk.wast:57 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxICBGNvcHkAAAdsb2FkOF91AAEKFgIMACAAIAEgAvwKAAALBwAgAC0AAAsLCgEAQQALBKq7zN0= +valid valid bulk.wast:115 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHEgIEaW5pdAAAB2xvYWQ4X3UAAQwBAQoWAgwAIAAgASAC/AgAAAsHACAALQAACwsHAQEEqrvM3Q== +valid valid bulk.wast:154 AGFzbQEAAAABCAJgAABgAX8AAwUEAAEAAQUDAQABBzsEDGRyb3BfcGFzc2l2ZQAADGluaXRfcGFzc2l2ZQABC2Ryb3BfYWN0aXZlAAILaW5pdF9hY3RpdmUAAwwBAgonBAUA/AkACwwAQQBBACAA/AgAAAsFAPwJAQsMAEEAQQAgAPwIAQALCwoCAQF4AEEACwF4 +valid valid bulk.wast:181 AGFzbQEAAAABBAFgAAADAgEADAFBCgcBBQD8CUALC4MBQQEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA= +valid valid bulk.wast:196 AGFzbQEAAAABBAFgAAADAgEADAEBCgcBBQD8CQALCwoBAQdnb29kYnll +valid valid bulk.wast:199 AGFzbQEAAAABEANgAAF/YAN/f38AYAF/AX8DBQQAAAECBAQBcAADBw8CBGluaXQAAgRjYWxsAAMJCAEBAAQAAQABCiAEBABBAAsEAEEBCwwAIAAgASAC/AwAAAsHACAAEQAACw== +valid valid bulk.wast:244 AGFzbQEAAAABCAJgAABgAX8AAwYFAAABAAEEBAFwAAEHOwQMZHJvcF9wYXNzaXZlAAEMaW5pdF9wYXNzaXZlAAILZHJvcF9hY3RpdmUAAwtpbml0X2FjdGl2ZQAECQsCAQABAABBAAsBAAoqBQIACwUA/A0ACwwAQQBBACAA/AwAAAsFAPwNAQsMAEEAQQAgAPwMAQAL +valid valid bulk.wast:274 AGFzbQEAAAABBAFgAAADAgEACcQBQQEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAoHAQUA/A1ACw== +valid valid bulk.wast:297 AGFzbQEAAAABBAFgAAADAgEACQUBAQABAAoHAQUA/A0ACw== +valid valid bulk.wast:300 AGFzbQEAAAABEANgAAF/YAN/f38AYAF/AX8DBgUAAAABAgQEAXAACgcPAgRjb3B5AAMEY2FsbAAECQkBAEEACwMAAQIKJQUEAEEACwQAQQELBABBAgsMACAAIAEgAvwOAAALBwAgABEAAAs= +valid valid call.wast:3 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= +invalid invalid call.wast:381 AGFzbQEAAAABBAFgAAADAwIAAAoKAgUAEAFFCwIACw== +invalid invalid call.wast:388 AGFzbQEAAAABCAJgAABgAAF+AwMCAAEKDAIFABABRQsEAEIBCw== +invalid invalid call.wast:396 AGFzbQEAAAABCAJgAABgAX8AAwMCAAEKCQIEABABCwIACw== +invalid invalid call.wast:403 AGFzbQEAAAABCQJgAABgAnx/AAMDAgABCgkCBAAQAQsCAAs= +invalid invalid call.wast:410 AGFzbQEAAAABBAFgAAADAwIAAAoLAgYAQQEQAQsCAAs= +invalid invalid call.wast:417 AGFzbQEAAAABBAFgAAADAwIAAAoUAg8ARAAAAAAAAABAQQEQAQsCAAs= +invalid invalid call.wast:425 AGFzbQEAAAABCQJgAABgAn9/AAMDAgABCgwCBwABQQEQAQsCAAs= +invalid invalid call.wast:432 AGFzbQEAAAABCQJgAABgAn9/AAMDAgABCgwCBwBBAQEQAQsCAAs= +invalid invalid call.wast:439 AGFzbQEAAAABCQJgAABgAn98AAMDAgABChQCDwBEAAAAAAAA8D9BARABCwIACw== +invalid invalid call.wast:446 AGFzbQEAAAABCQJgAABgAnx/AAMDAgABChQCDwBBAUQAAAAAAADwPxABCwIACw== +invalid invalid call.wast:454 AGFzbQEAAAABCAJgAABgAX8AAwMCAAEKDAIHAAJAEAELCwIACw== +invalid invalid call.wast:463 AGFzbQEAAAABCQJgAABgAn9/AAMDAgABCg4CCQACQEEAEAELCwIACw== +invalid invalid call.wast:472 AGFzbQEAAAABCAJgAABgAX8AAwMCAAEKDAIHAANAEAELCwIACw== +invalid invalid call.wast:481 AGFzbQEAAAABCQJgAABgAn9/AAMDAgABCg4CCQADQEEAEAELCwIACw== +invalid invalid call.wast:490 AGFzbQEAAAABCAJgAABgAX8AAwMCAAEKDgIJAEEABEAQAQsLAgAL +invalid invalid call.wast:499 AGFzbQEAAAABCQJgAABgAn9/AAMDAgABChACCwBBAARAQQAQAQsLAgAL +invalid invalid call.wast:512 AGFzbQEAAAABBAFgAAADAgEACgYBBAAQAQs= +invalid invalid call.wast:516 AGFzbQEAAAABBAFgAAADAgEACgoBCAAQlJjb4gML +valid valid call_indirect.wast:3 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 +valid valid call_indirect.wast:623 AGFzbQEAAAABEQNgAn9/AX9gAABgA39/fwF/AwoJAAAAAAABAgICBAwDcAECAnABAwNwAAQHHAMGY2FsbC0xAAYGY2FsbC0yAAcGY2FsbC0zAAgJIwQAQQALAgABAgFBAAsAAwIDBAICQQALAAIBAgICQQMLAAEFClAJBwAgACABagsHACAAIAFrCwcAIAAgAWwLBwAgACABbgsHACAAIAFwCwIACwsAIAAgASACEQAACwsAIAAgASACEQABCwsAIAAgASACEQACCw== +invalid invalid call_indirect.wast:791 AGFzbQEAAAABBAFgAAADAgEACgkBBwBBABEAAAs= +invalid invalid call_indirect.wast:799 AGFzbQEAAAABBAFgAAADAgEABAQBcAAACgoBCABBABEAAEUL +invalid invalid call_indirect.wast:807 AGFzbQEAAAABCAJgAAF+YAAAAwIBAQQEAXAAAAoKAQgAQQARAABFCw== +invalid invalid call_indirect.wast:816 AGFzbQEAAAABCAJgAX8AYAAAAwIBAQQEAXAAAAoJAQcAQQARAAAL +invalid invalid call_indirect.wast:824 AGFzbQEAAAABCQJgAnx/AGAAAAMCAQEEBAFwAAAKCQEHAEEAEQAACw== +invalid invalid call_indirect.wast:832 AGFzbQEAAAABBAFgAAADAgEABAQBcAAACgsBCQBBAUEAEQAACw== +invalid invalid call_indirect.wast:840 AGFzbQEAAAABBAFgAAADAgEABAQBcAAAChQBEgBEAAAAAAAAAEBBAUEAEQAACw== +invalid invalid call_indirect.wast:851 AGFzbQEAAAABCAJgAX8AYAAAAwIBAQQEAXAAAAoKAQgAQQEBEQAACw== +invalid invalid call_indirect.wast:859 AGFzbQEAAAABCAJgAX8AYAAAAwIBAQQEAXAAAAoLAQkAQQBCAREAAAs= +invalid invalid call_indirect.wast:868 AGFzbQEAAAABCQJgAn9/AGAAAAMCAQEEBAFwAAAKDAEKAAFBAUEAEQAACw== +invalid invalid call_indirect.wast:878 AGFzbQEAAAABCQJgAn9/AGAAAAMCAQEEBAFwAAAKDAEKAEEBAUEAEQAACw== +invalid invalid call_indirect.wast:888 AGFzbQEAAAABCQJgAn98AGAAAAMCAQEEBAFwAAAKFAESAEQAAAAAAADwP0EBQQARAAAL +invalid invalid call_indirect.wast:898 AGFzbQEAAAABCQJgAnx/AGAAAAMCAQEEBAFwAAAKFAESAEEBRAAAAAAAAPA/QQARAAAL +invalid invalid call_indirect.wast:909 AGFzbQEAAAABCAJgAX8AYAAAAwMCAAEEBQFwAQEBCQcBAEEACwEACg8CAgALCgACQEEAEQAACws= +invalid invalid call_indirect.wast:922 AGFzbQEAAAABCQJgAn9/AGAAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoRAgIACwwAAkBBAEEAEQAACws= +invalid invalid call_indirect.wast:935 AGFzbQEAAAABCAJgAX8AYAAAAwMCAAEEBQFwAQEBCQcBAEEACwEACg8CAgALCgADQEEAEQAACws= +invalid invalid call_indirect.wast:948 AGFzbQEAAAABCQJgAn9/AGAAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoRAgIACwwAA0BBAEEAEQAACws= +invalid invalid call_indirect.wast:961 AGFzbQEAAAABCAJgAX8AYAAAAwMCAAEEBQFwAQEBCQcBAEEACwEAChMCAgALDgBBAEEABEBBABEAAAsL +invalid invalid call_indirect.wast:977 AGFzbQEAAAABCQJgAn9/AGAAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoVAgIACxAAQQBBAARAQQBBABEAAAsL +invalid invalid call_indirect.wast:995 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCgkBBwBBABEAAAs= +invalid invalid call_indirect.wast:1006 AGFzbQEAAAABBAFgAAADAgEABAQBcAAACgkBBwBBABEBAAs= +invalid invalid call_indirect.wast:1013 AGFzbQEAAAABBAFgAAADAgEABAQBcAAACg0BCwBBABGUmNviAwAL +invalid invalid call_indirect.wast:1022 AGFzbQEAAAABCAJgAX8AYAAAAwIBAQQEAXAAAQoSARAAQQEEQEEAEf////8PAAsL +invalid invalid call_indirect.wast:1038 AGFzbQEAAAAEBQFwAQICCQgBAEEACwIAAA== +valid valid call_indirect.wast:1047 AGFzbQEAAAABCAJgAABgAAF/AwwLAAAAAAAAAAAAAQEEBAFwAAEKZgsGAAARAAALBwAAEQAAAQsJAAARAAARAAALCQAAEQAAEQAACwwAABEAABEAABEAAAsGAAARAAALBgAAEQAACwkAABEAABEAAAsJAAARAAARAAALBwAAEQAAGwsKAAARAAAbEQAACw== +valid valid comments.wast:0 AGFzbQEAAAABBQFgAAF/AwQDAAAABxADAmYxAAACZjIAAQJmMwACChkDBwBBAUECDwsHAEEBQQIPCwcAQQFBAg8L +valid valid const.wast:5 AGFzbQEAAAABBAFgAAADAgEACgoBCABBlZrvOhoL +valid valid const.wast:6 AGFzbQEAAAABBAFgAAADAgEACgsBCQBB3/e/1nkaCw== +valid valid const.wast:24 AGFzbQEAAAABBAFgAAADAgEACgoBCABClZrvOhoL +valid valid const.wast:25 AGFzbQEAAAABBAFgAAADAgEACg8BDQBC7/m+75rx2ZIBGgs= +valid valid const.wast:43 AGFzbQEAAAABBAFgAAADAgEACgoBCABDo3nrTBoL +valid valid const.wast:44 AGFzbQEAAAABBAFgAAADAgEACgoBCABDe01/bBoL +valid valid const.wast:46 AGFzbQEAAAABBAFgAAADAgEACgoBCABD/y9ZLRoL +valid valid const.wast:55 AGFzbQEAAAABBAFgAAADAgEACgoBCABDtKKRWxoL +valid valid const.wast:56 AGFzbQEAAAABBAFgAAADAgEACgoBCABDtKIRZRoL +valid valid const.wast:58 AGFzbQEAAAABBAFgAAADAgEACgoBCABDtKIRUhoL +valid valid const.wast:145 AGFzbQEAAAABBAFgAAADAgEACg4BDABEAAAAVDRvnUEaCw== +valid valid const.wast:146 AGFzbQEAAAABBAFgAAADAgEACg4BDABE+hZeW6/pj0UaCw== +valid valid const.wast:148 AGFzbQEAAAABBAFgAAADAgEACg4BDABEEUMr1v8lqz0aCw== +valid valid const.wast:153 AGFzbQEAAAABBAFgAAADAgEACg4BDABEWKQMVDRvnUEaCw== +valid valid const.wast:154 AGFzbQEAAAABBAFgAAADAgEACg4BDABE1ctrW6/pj0UaCw== +valid valid const.wast:156 AGFzbQEAAAABBAFgAAADAgEACg4BDABEEuw21v8lqz0aCw== +valid valid const.wast:164 AGFzbQEAAAABBAFgAAADAgEACg4BDABE37yaeFY08kQaCw== +valid valid const.wast:165 AGFzbQEAAAABBAFgAAADAgEACg4BDABE37yaeFY0IkYaCw== +valid valid const.wast:167 AGFzbQEAAAABBAFgAAADAgEACg4BDABE37yaeFY0wkMaCw== +valid valid const.wast:264 AGFzbQEAAAABBAFgAAADAgEACgcBBQBBfxoL +valid valid const.wast:265 AGFzbQEAAAABBAFgAAADAgEACgsBCQBBgICAgHgaCw== +valid valid const.wast:286 AGFzbQEAAAABBAFgAAADAgEACgcBBQBCfxoL +valid valid const.wast:287 AGFzbQEAAAABBAFgAAADAgEAChABDgBCgICAgICAgICAfxoL +valid valid const.wast:308 AGFzbQEAAAABBAFgAAADAgEACgoBCABDAAAAfxoL +valid valid const.wast:309 AGFzbQEAAAABBAFgAAADAgEACgoBCABDAAAA/xoL +valid valid const.wast:310 AGFzbQEAAAABBAFgAAADAgEACgoBCABD//9/fxoL +valid valid const.wast:311 AGFzbQEAAAABBAFgAAADAgEACgoBCABD//9//xoL +valid valid const.wast:335 AGFzbQEAAAABBAFgAAADAgEACgoBCABDmXaWfhoL +valid valid const.wast:336 AGFzbQEAAAABBAFgAAADAgEACgoBCABDmXaW/hoL +valid valid const.wast:357 AGFzbQEAAAABBAFgAAADAgEACg4BDABEAAAAAAAA4H8aCw== +valid valid const.wast:358 AGFzbQEAAAABBAFgAAADAgEACg4BDABEAAAAAAAA4P8aCw== +valid valid const.wast:359 AGFzbQEAAAABBAFgAAADAgEACg4BDABE////////738aCw== +valid valid const.wast:360 AGFzbQEAAAABBAFgAAADAgEACg4BDABE////////7/8aCw== +valid valid const.wast:382 AGFzbQEAAAABBAFgAAADAgEACg4BDABEoMjrhfPM4X8aCw== +valid valid const.wast:383 AGFzbQEAAAABBAFgAAADAgEACg4BDABEoMjrhfPM4f8aCw== +valid valid const.wast:404 AGFzbQEAAAABBAFgAAADAgEACgoBCABDAQCAfxoL +valid valid const.wast:405 AGFzbQEAAAABBAFgAAADAgEACg4BDABEAQAAAAAA8H8aCw== +valid valid const.wast:406 AGFzbQEAAAABBAFgAAADAgEACgoBCABD////fxoL +valid valid const.wast:407 AGFzbQEAAAABBAFgAAADAgEACg4BDABE/////////38aCw== +valid valid const.wast:440 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMAAIAmCw== +valid valid const.wast:442 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMAAICmCw== +valid valid const.wast:444 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMBAIAmCw== +valid valid const.wast:446 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMBAICmCw== +valid valid const.wast:464 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMCAIAmCw== +valid valid const.wast:466 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMCAICmCw== +valid valid const.wast:492 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMDAIAmCw== +valid valid const.wast:494 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMDAICmCw== +valid valid const.wast:564 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMAAIBYCw== +valid valid const.wast:566 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMAAIDYCw== +valid valid const.wast:568 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMBAIBYCw== +valid valid const.wast:570 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMBAIDYCw== +valid valid const.wast:588 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMCAIBYCw== +valid valid const.wast:590 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMCAIDYCw== +valid valid const.wast:616 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMDAIBYCw== +valid valid const.wast:618 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMDAIDYCw== +valid valid const.wast:668 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMAAAAACw== +valid valid const.wast:670 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMAAACACw== +valid valid const.wast:672 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMBAAAACw== +valid valid const.wast:674 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMBAACACw== +valid valid const.wast:692 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMCAAAACw== +valid valid const.wast:694 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMCAACACw== +valid valid const.wast:720 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMDAAAACw== +valid valid const.wast:722 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEMDAACACw== +valid valid const.wast:726 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEP//39/Cw== +valid valid const.wast:728 AGFzbQEAAAABBQFgAAF9AwIBAAcFAQFmAAAKCQEHAEP//3//Cw== +valid valid const.wast:740 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAABwGgs= +valid valid const.wast:742 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAABwmgs= +valid valid const.wast:744 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAABwGgs= +valid valid const.wast:746 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAABwmgs= +valid valid const.wast:764 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAABwGgs= +valid valid const.wast:766 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAABwmgs= +valid valid const.wast:788 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAABwGgs= +valid valid const.wast:790 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAABwmgs= +valid valid const.wast:846 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAABgfgs= +valid valid const.wast:848 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAABg/gs= +valid valid const.wast:850 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAABgfgs= +valid valid const.wast:852 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAABg/gs= +valid valid const.wast:858 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAABgfgs= +valid valid const.wast:860 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAABg/gs= +valid valid const.wast:864 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAABwZQs= +valid valid const.wast:866 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAABw5Qs= +valid valid const.wast:868 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAABwZQs= +valid valid const.wast:870 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAABw5Qs= +valid valid const.wast:888 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAABwZQs= +valid valid const.wast:890 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAABw5Qs= +valid valid const.wast:916 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAABwZQs= +valid valid const.wast:918 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAABw5Qs= +valid valid const.wast:921 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAAAARgs= +valid valid const.wast:923 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAAAAxgs= +valid valid const.wast:925 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAAAARgs= +valid valid const.wast:927 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAAAAxgs= +valid valid const.wast:945 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAAAARgs= +valid valid const.wast:947 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAAAAxgs= +valid valid const.wast:973 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAAAARgs= +valid valid const.wast:975 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAAAAxgs= +valid valid const.wast:978 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAACwQws= +valid valid const.wast:980 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAACwwws= +valid valid const.wast:982 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAACwQws= +valid valid const.wast:984 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAACwwws= +valid valid const.wast:990 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAACwQws= +valid valid const.wast:992 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAACwwws= +valid valid const.wast:996 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAAAAAAs= +valid valid const.wast:998 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQAAAAAAAAAgAs= +valid valid const.wast:1000 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAAAAAAs= +valid valid const.wast:1002 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQBAAAAAAAAgAs= +valid valid const.wast:1020 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAAAAAAs= +valid valid const.wast:1022 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQCAAAAAAAAgAs= +valid valid const.wast:1048 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAAAQAAs= +valid valid const.wast:1050 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAEQDAAAAAAAQgAs= +valid valid const.wast:1054 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAET////////vfws= +valid valid const.wast:1056 AGFzbQEAAAABBQFgAAF8AwIBAAcFAQFmAAAKDQELAET////////v/ws= +valid valid conversions.wast:1 AGFzbQEAAAABPQxgAX8BfmABfgF/YAF9AX9gAXwBf2ABfQF+YAF8AX5gAX8BfWABfgF9YAF/AXxgAX4BfGABfQF8YAF8AX0DIiEAAAECAgMDBAQFBQICAwMEBAUFBgcICQYHCAkKCwYJAgUHkQUhEGk2NC5leHRlbmRfaTMyX3MAABBpNjQuZXh0ZW5kX2kzMl91AAEMaTMyLndyYXBfaTY0AAIPaTMyLnRydW5jX2YzMl9zAAMPaTMyLnRydW5jX2YzMl91AAQPaTMyLnRydW5jX2Y2NF9zAAUPaTMyLnRydW5jX2Y2NF91AAYPaTY0LnRydW5jX2YzMl9zAAcPaTY0LnRydW5jX2YzMl91AAgPaTY0LnRydW5jX2Y2NF9zAAkPaTY0LnRydW5jX2Y2NF91AAoTaTMyLnRydW5jX3NhdF9mMzJfcwALE2kzMi50cnVuY19zYXRfZjMyX3UADBNpMzIudHJ1bmNfc2F0X2Y2NF9zAA0TaTMyLnRydW5jX3NhdF9mNjRfdQAOE2k2NC50cnVuY19zYXRfZjMyX3MADxNpNjQudHJ1bmNfc2F0X2YzMl91ABATaTY0LnRydW5jX3NhdF9mNjRfcwARE2k2NC50cnVuY19zYXRfZjY0X3UAEhFmMzIuY29udmVydF9pMzJfcwATEWYzMi5jb252ZXJ0X2k2NF9zABQRZjY0LmNvbnZlcnRfaTMyX3MAFRFmNjQuY29udmVydF9pNjRfcwAWEWYzMi5jb252ZXJ0X2kzMl91ABcRZjMyLmNvbnZlcnRfaTY0X3UAGBFmNjQuY29udmVydF9pMzJfdQAZEWY2NC5jb252ZXJ0X2k2NF91ABoPZjY0LnByb21vdGVfZjMyABsOZjMyLmRlbW90ZV9mNjQAHBNmMzIucmVpbnRlcnByZXRfaTMyAB0TZjY0LnJlaW50ZXJwcmV0X2k2NAAeE2kzMi5yZWludGVycHJldF9mMzIAHxNpNjQucmVpbnRlcnByZXRfZjY0ACAKzwEhBQAgAKwLBQAgAK0LBQAgAKcLBQAgAKgLBQAgAKkLBQAgAKoLBQAgAKsLBQAgAK4LBQAgAK8LBQAgALALBQAgALELBgAgAPwACwYAIAD8AQsGACAA/AILBgAgAPwDCwYAIAD8BAsGACAA/AULBgAgAPwGCwYAIAD8BwsFACAAsgsFACAAtAsFACAAtwsFACAAuQsFACAAswsFACAAtQsFACAAuAsFACAAugsFACAAuwsFACAAtgsFACAAvgsFACAAvwsFACAAvAsFACAAvQs= +invalid invalid conversions.wast:678 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAQwAAAACnCw== +invalid invalid conversions.wast:679 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgCoCw== +invalid invalid conversions.wast:680 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgCpCw== +invalid invalid conversions.wast:681 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgCqCw== +invalid invalid conversions.wast:682 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgCrCw== +invalid invalid conversions.wast:683 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgC8Cw== +invalid invalid conversions.wast:684 AGFzbQEAAAABBQFgAAF+AwIBAAoKAQgAQwAAAACsCw== +invalid invalid conversions.wast:685 AGFzbQEAAAABBQFgAAF+AwIBAAoKAQgAQwAAAACtCw== +invalid invalid conversions.wast:686 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQCuCw== +invalid invalid conversions.wast:687 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQCvCw== +invalid invalid conversions.wast:688 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQCwCw== +invalid invalid conversions.wast:689 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQCxCw== +invalid invalid conversions.wast:690 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQC9Cw== +invalid invalid conversions.wast:691 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCyCw== +invalid invalid conversions.wast:692 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCzCw== +invalid invalid conversions.wast:693 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQQC0Cw== +invalid invalid conversions.wast:694 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQQC1Cw== +invalid invalid conversions.wast:695 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQQC2Cw== +invalid invalid conversions.wast:696 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgC+Cw== +invalid invalid conversions.wast:697 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgC3Cw== +invalid invalid conversions.wast:698 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgC4Cw== +invalid invalid conversions.wast:699 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQQC5Cw== +invalid invalid conversions.wast:700 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQQC6Cw== +invalid invalid conversions.wast:701 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQQC7Cw== +invalid invalid conversions.wast:702 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQQC/Cw== +valid valid custom.wast:1 AGFzbQEAAAAAJBBhIGN1c3RvbSBzZWN0aW9udGhpcyBpcyB0aGUgcGF5bG9hZAAgEGEgY3VzdG9tIHNlY3Rpb250aGlzIGlzIHBheWxvYWQAERBhIGN1c3RvbSBzZWN0aW9uABAAdGhpcyBpcyBwYXlsb2FkAAEAACQQAABjdXN0b20gc2VjdGlvAHRoaXMgaXMgdGhlIHBheWxvYWQAJBDvu79hIGN1c3RvbSBzZWN0dGhpcyBpcyB0aGUgcGF5bG9hZAAkEGEgY3VzdG9tIHNlY3TijKN0aGlzIGlzIHRoZSBwYXlsb2FkAB8WbW9kdWxlIHdpdGhpbiBhIG1vZHVsZQBhc20BAAAA +valid valid custom.wast:14 AGFzbQEAAAAADgZjdXN0b21wYXlsb2FkAA4GY3VzdG9tcGF5bG9hZAEBAAAOBmN1c3RvbXBheWxvYWQADgZjdXN0b21wYXlsb2FkAgEAAA4GY3VzdG9tcGF5bG9hZAAOBmN1c3RvbXBheWxvYWQDAQAADgZjdXN0b21wYXlsb2FkAA4GY3VzdG9tcGF5bG9hZAQBAAAOBmN1c3RvbXBheWxvYWQADgZjdXN0b21wYXlsb2FkBQEAAA4GY3VzdG9tcGF5bG9hZAAOBmN1c3RvbXBheWxvYWQGAQAADgZjdXN0b21wYXlsb2FkAA4GY3VzdG9tcGF5bG9hZAcBAAAOBmN1c3RvbXBheWxvYWQADgZjdXN0b21wYXlsb2FkCQEAAA4GY3VzdG9tcGF5bG9hZAAOBmN1c3RvbXBheWxvYWQKAQAADgZjdXN0b21wYXlsb2FkAA4GY3VzdG9tcGF5bG9hZAsBAAAOBmN1c3RvbXBheWxvYWQADgZjdXN0b21wYXlsb2Fk +valid valid custom.wast:50 AGFzbQEAAAABBwFgAn9/AX8AGgZjdXN0b210aGlzIGlzIHRoZSBwYXlsb2FkAwIBAAcKAQZhZGRUd28AAAoJAQcAIAAgAWoLABsHY3VzdG9tMnRoaXMgaXMgdGhlIHBheWxvYWQ= +malformed invalid custom.wast:61 AGFzbQEAAAAA +malformed invalid custom.wast:69 AGFzbQEAAAAAAA== +malformed invalid custom.wast:77 AGFzbQEAAAAAAAAFAQAHAAA= +malformed invalid custom.wast:85 AGFzbQEAAAAAJhBhIGN1c3RvbSBzZWN0aW9udGhpcyBpcyB0aGUgcGF5bG9hZA== +malformed invalid custom.wast:93 AGFzbQEAAAAAJRBhIGN1c3RvbSBzZWN0aW9udGhpcyBpcyB0aGUgcGF5bG9hZAAkEGEgY3VzdG9tIHNlY3Rpb250aGlzIGlzIHRoZSBwYXlsb2Fk +malformed invalid custom.wast:102 AGFzbQEAAAABBwFgAn9/AX8AJRBhIGN1c3RvbSBzZWN0aW9udGhpcyBpcyB0aGUgcGF5bG9hZAMCAQAKCQEHACAAIAFqCwAbB2N1c3RvbTJ0aGlzIGlzIHRoZSBwYXlsb2Fk +malformed invalid custom.wast:115 AGFzbQEAAAAAYXNtAQAAAA== +malformed invalid custom.wast:123 AGFzbQEAAAAFAwEAAQwBAgsGAQBBAAsA +valid valid data.wast:5 AGFzbQEAAAAFAwEAAQuzARwAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMBAAEEYWJjZABBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwEAAQRhYmNk +valid valid data.wast:39 AGFzbQEAAAAFAwEAAQsHAQBBAAsBYQ== +valid valid data.wast:43 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwcBAEEACwFh +valid valid data.wast:48 AGFzbQEAAAAFAwEAAQsiBQBBAAsBYQBBAwsBYgBB5AALA2NkZQBBBQsBeABBAwsBYw== +valid valid data.wast:56 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCycGAEEACwFhAEEBCwFiAEECCwNjZGUAQQMLAWYAQQILAWcAQQELAWg= +valid valid data.wast:66 AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQABCwcBACMACwFh +valid valid data.wast:71 AGFzbQEAAAACKwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAAELBwEAIwALAWE= +invalid valid data.wast:89 AGFzbQEAAAAFAwEAAQYGAX8AQQALCwcBACMACwFh +valid valid data.wast:100 AGFzbQEAAAAFAwEAAQsPAgBBAAsBYQBB//8DCwFi +valid valid data.wast:105 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCw8CAEEACwFhAEH//wMLAWI= +valid valid data.wast:111 AGFzbQEAAAAFAwEAAgsJAQBB//8HCwFh +valid valid data.wast:116 AGFzbQEAAAAFAwEAAAsGAQBBAAsA +valid valid data.wast:120 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwYBAEEACwA= +valid valid data.wast:125 AGFzbQEAAAAFBAEBAAALBgEAQQALAA== +valid valid data.wast:130 AGFzbQEAAAAFAwEAAQsIAQBBgIAECwA= +valid valid data.wast:149 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwcBAEEACwFh +valid valid data.wast:154 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAwsHAQBBAAsBYQ== +valid valid data.wast:159 AGFzbQEAAAACKwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAAALBwEAIwALAWE= +valid valid data.wast:165 AGFzbQEAAAACLAIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAQADCwcBACMACwFh +valid valid data.wast:171 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwcBAEEBCwFh +valid valid data.wast:176 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAwsHAQBBAQsBYQ== +valid valid data.wast:184 AGFzbQEAAAAFAwEAAAsHAQBBAAsBYQ== +valid valid data.wast:192 AGFzbQEAAAAFBAEBAAALBwEAQQALAWE= +valid valid data.wast:200 AGFzbQEAAAAFBAEBAAELBwEAQQALAWE= +valid valid data.wast:207 AGFzbQEAAAAFAwEAAAsGAQBBAQsA +valid valid data.wast:214 AGFzbQEAAAAFBAEBAAELBgEAQQELAA== +valid valid data.wast:231 AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQAACwcBACMACwFh +valid valid data.wast:240 AGFzbQEAAAAFBAEBAQILCQEAQYCABAsBYQ== +valid valid data.wast:247 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwkBAEGAgAQLAWE= +valid valid data.wast:255 AGFzbQEAAAAFAwEAAgsJAQBBgIAICwFh +valid valid data.wast:263 AGFzbQEAAAAFBAEBAgMLCQEAQYCACAsBYQ== +valid valid data.wast:271 AGFzbQEAAAAFAwEAAQsHAQBBfwsBYQ== +valid valid data.wast:278 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwcBAEF/CwFh +valid valid data.wast:286 AGFzbQEAAAAFAwEAAgsIAQBBnH8LAWE= +valid valid data.wast:293 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwgBAEGcfwsBYQ== +invalid invalid data.wast:303 AGFzbQEAAAALBgEAQQALAA== +invalid invalid data.wast:311 AGFzbQEAAAAFAwEAAAsHAQIBQQALAA== +invalid invalid data.wast:335 AGFzbQEAAAALBwECAUEACwA= +invalid invalid data.wast:347 AGFzbQEAAAAFAwEAAAtFAQIBQQALPgABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9 +invalid invalid data.wast:369 AGFzbQEAAAALRQECAUEACz4AAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2Nzg5Ojs8PQ== +invalid invalid data.wast:388 AGFzbQEAAAAFAwEAAQsGAQBCAAsA +invalid invalid data.wast:396 AGFzbQEAAAAFAwEAAQsGAQDQcAsA +invalid invalid data.wast:404 AGFzbQEAAAAFAwEAAQsEAQALAA== +invalid invalid data.wast:412 AGFzbQEAAAAFAwEAAQsIAQBBAEEACwA= +invalid invalid data.wast:420 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABQMBAAELCAEAIwAjAAsA +invalid invalid data.wast:429 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABQMBAAELCAEAIwBBAAsA +invalid invalid data.wast:438 AGFzbQEAAAAFAwEAAQsHAQBBAGgLAA== +invalid invalid data.wast:446 AGFzbQEAAAAFAwEAAQsFAQABCwA= +invalid invalid data.wast:454 AGFzbQEAAAAFAwEAAQsHAQABQQALAA== +invalid invalid data.wast:462 AGFzbQEAAAAFAwEAAQsHAQBBAAELAA== +invalid invalid data.wast:470 AGFzbQEAAAACCwEEdGVzdAFnA38BBQMBAAELBgEAIwALAA== +invalid invalid data.wast:479 AGFzbQEAAAAFAwEAAQsGAQAjAAsA +invalid invalid data.wast:487 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABQMBAAELBgEAIwELAA== +invalid invalid data.wast:496 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWkzMgN/AQUDAQABCwYBACMACwA= +valid valid elem.wast:4 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgmkAz0BAAAFcATSAAvSAAvQcAvSAQsBAAABAAQAAAEBAQAABXAE0gAL0gAL0HAL0gELAQAAAQAEAAABAQBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAEQQALAtIAC9BwCwBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAADAAAHcATSAAvSAAvQcAvSAQsDAAADAAQAAAEBAwAAB3AE0gAL0gAL0HAL0gELAwAAAwAEAAABAQoHAgIACwIACw== +valid valid elem.wast:80 AGFzbQEAAAABBAFgAAADAwIAAAQFAXABAwMJDwEEQQALA9IAC9BwC9IBCwoHAgIACwIACw== +valid valid elem.wast:90 AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEACwEACgQBAgAL +valid valid elem.wast:95 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBAAsBAAoEAQIACw== +valid valid elem.wast:101 AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCR8FAEEACwEAAEEDCwEAAEEHCwEAAEEFCwEAAEEDCwEACgQBAgAL +valid valid elem.wast:110 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkfBQBBCQsBAABBAwsBAABBBwsBAABBAwsBAABBBQsBAAoEAQIACw== +valid valid elem.wast:120 AGFzbQEAAAABBAFgAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMCAQAEBQFwAOgHCQcBACMACwEACgQBAgAL +valid valid elem.wast:134 AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACgcTAgZjYWxsLTcAAgZjYWxsLTkAAwkNAgBBBwsBAABBCQsBAQodBAUAQcEACwUAQcIACwcAQQcRAAALBwBBCREAAAs= +valid valid elem.wast:153 AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACwcTAgZjYWxsLTcAAgZjYWxsLTkAAwkXAgRBBgsC0HAL0gALBEEJCwLSAQvQcAsKHQQFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQkRAAAL +invalid valid elem.wast:171 AGFzbQEAAAABBAFgAAADAgEABAQBcAABBgYBfwBBAAsJBwEAIwALAQAKBAECAAs= +valid valid elem.wast:182 AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEJCwEACgQBAgAL +valid valid elem.wast:187 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBCQsBAAoEAQIACw== +valid valid elem.wast:193 AGFzbQEAAAAEBAFwAAAJBgEAQQALAA== +valid valid elem.wast:197 AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAACQYBAEEACwA= +valid valid elem.wast:202 AGFzbQEAAAAEBQFwAQAACQYBAEEACwA= +valid valid elem.wast:207 AGFzbQEAAAAEBAFwABQJBgEAQRQLAA== +valid valid elem.wast:212 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAAAwIBAAkHAQBBAAsBAAoEAQIACw== +valid valid elem.wast:218 AGFzbQEAAAABBAFgAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAZAMCAQAJBwEAQQALAQAKBAECAAs= +valid valid elem.wast:224 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAAAwIBAAkHAQBBAQsBAAoEAQIACw== +valid valid elem.wast:230 AGFzbQEAAAABBAFgAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAHgMCAQAJBwEAQQELAQAKBAECAAs= +valid valid elem.wast:239 AGFzbQEAAAABBAFgAAADAgEABAQBcAAACQcBAEEACwEACgQBAgAL +valid valid elem.wast:248 AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAAkHAQBBAAsBAAoEAQIACw== +valid valid elem.wast:257 AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAQkHAQBBAAsBAAoEAQIACw== +valid valid elem.wast:266 AGFzbQEAAAAEBAFwAAAJBgEAQQELAA== +valid valid elem.wast:273 AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEKCwEACgQBAgAL +valid valid elem.wast:281 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBCgsBAAoEAQIACw== +valid valid elem.wast:290 AGFzbQEAAAABBAFgAAADAgEABAUBcAEKFAkHAQBBCgsBAAoEAQIACw== +valid valid elem.wast:307 AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF/CwEACgQBAgAL +valid valid elem.wast:315 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBfwsBAAoEAQIACw== +valid valid elem.wast:324 AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF2CwEACgQBAgAL +valid valid elem.wast:332 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBdgsBAAoEAQIACw== +valid valid elem.wast:342 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBwEAQQALAQAKEQICAAsMAEEAQQBBAfwMAAAL +valid valid elem.wast:352 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBQEDAAEAChECAgALDABBAEEAQQH8DAAACw== +invalid invalid elem.wast:365 AGFzbQEAAAABBAFgAAADAgEACQcBAEEACwEACgQBAgAL +invalid invalid elem.wast:375 AGFzbQEAAAAEBAFwAAEJBgEAQgALAA== +invalid invalid elem.wast:383 AGFzbQEAAAAEBAFwAAEJBgEA0HALAA== +invalid invalid elem.wast:391 AGFzbQEAAAAEBAFwAAEJBAEACwA= +invalid invalid elem.wast:399 AGFzbQEAAAAEBAFwAAEJCAEAQQBBAAsA +invalid invalid elem.wast:407 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABAQBcAABCQgBACMAIwALAA== +invalid invalid elem.wast:416 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABAQBcAABCQgBACMAQQALAA== +invalid invalid elem.wast:426 AGFzbQEAAAAEBAFwAAEJBwEAQQBoCwA= +invalid invalid elem.wast:434 AGFzbQEAAAAEBAFwAAEJBQEAAQsA +invalid invalid elem.wast:442 AGFzbQEAAAAEBAFwAAEJBwEAAUEACwA= +invalid invalid elem.wast:450 AGFzbQEAAAAEBAFwAAEJBwEAQQABCwA= +invalid invalid elem.wast:458 AGFzbQEAAAACCwEEdGVzdAFnA38BBAQBcAABCQYBACMACwA= +invalid invalid elem.wast:467 AGFzbQEAAAAEBAFwAAEJBgEAIwALAA== +invalid invalid elem.wast:475 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABAQBcAABCQYBACMBCwA= +invalid invalid elem.wast:484 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWkzMgN/AQQEAXAAAQkGAQAjAAsA +invalid invalid elem.wast:495 AGFzbQEAAAAEBAFwAAEJCQEEQQALAdBvCw== +invalid invalid elem.wast:503 AGFzbQEAAAAEBAFwAAEJCwEEQQALAdBw0HAL +invalid invalid elem.wast:511 AGFzbQEAAAAEBAFwAAEJCQEEQQALAUEACw== +invalid invalid elem.wast:527 AGFzbQEAAAABBQFgAAFwAwIBAAQEAXAAAQkJAQRBAAsBEAALCgYBBADQcAs= +valid valid elem.wast:537 AGFzbQEAAAABBQFgAAF/AwQDAAAABAQBcAAKBxQBEGNhbGwtb3ZlcndyaXR0ZW4AAgkNAgBBCQsBAABBCQsBAQoVAwUAQcEACwUAQcIACwcAQQkRAAAL +valid valid elem.wast:550 AGFzbQEAAAABBQFgAAF/AhQBCHNwZWN0ZXN0BXRhYmxlAXAACgMEAwAAAAccARhjYWxsLW92ZXJ3cml0dGVuLWVsZW1lbnQAAgkNAgBBCQsBAABBCQsBAQoVAwUAQcEACwUAQcIACwcAQQkRAAAL +valid valid elem.wast:565 AGFzbQEAAAABBQFgAAF/AwYFAAAAAAAEBAFwAAoHKwQMc2hhcmVkLXRhYmxlAQAGY2FsbC03AAIGY2FsbC04AAMGY2FsbC05AAQJDQIAQQgLAQAAQQkLAQEKJQUFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQgRAAALBwBBCREAAAs= +valid valid elem.wast:589 AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEHCwEAAEEICwEBCg0CBQBBwwALBQBBxAAL +valid valid elem.wast:602 AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEICwEAAEEJCwEBCg0CBQBBxQALBQBBxgAL +invalid invalid elem.wast:618 AGFzbQEAAAABBAFgAAADAgEABAQBbwABCQcBAEEACwEACgQBAgAL +invalid invalid elem.wast:623 AGFzbQEAAAAEBAFwAAEJCwEGAEEAC28B0G8L +invalid invalid elem.wast:628 AGFzbQEAAAABBAFgAAADAwIAAAQEAW8AAQkFAQEAAQAKEQICAAsMAEEAQQBBAfwMAAAL +invalid invalid elem.wast:637 AGFzbQEAAAABBAFgAAADAgEABAQBcAABCQcBBW8B0G8LCg4BDABBAEEAQQH8DAAACw== +valid valid elem.wast:646 AGFzbQEAAAABCwJgAX8Bb2ACf28AAwMCAAEEBAFvAAIHFQMFdGFibGUBAANnZXQAAANzZXQAAQoRAgYAIAAlAAsIACAAIAEmAAs= +valid valid elem.wast:664 AGFzbQEAAAACFAEIZXhwb3J0ZXIFdGFibGUBbwACCQsBBgBBAAtvAdBvCw== +valid valid elem.wast:673 AGFzbQEAAAABBQFgAAF/AwIBAAYGAXAA0gALBwUBAWYDAAoGAQQAQSoL +valid valid elem.wast:682 AGFzbQEAAAABBQFgAAF/Ag4BB21vZHVsZTQBZgNwAAMCAQAEBAFwAAoHFgESY2FsbF9pbXBvcnRlZF9lbGVtAAAJCQEEQQALASMACwoJAQcAQQARAAAL +valid valid endianness.wast:1 AGFzbQEAAAABJAdgAn9/AGACf34AYAF/AX9gAX8BfmABfgF+YAF9AX1gAXwBfAMYFwAAAQICAwICAgQEBAQEBQYCAgQEBAUGBQMBAAEH4QERDGkzMl9sb2FkMTZfcwAGDGkzMl9sb2FkMTZfdQAHCGkzMl9sb2FkAAgMaTY0X2xvYWQxNl9zAAkMaTY0X2xvYWQxNl91AAoMaTY0X2xvYWQzMl9zAAsMaTY0X2xvYWQzMl91AAwIaTY0X2xvYWQADQhmMzJfbG9hZAAOCGY2NF9sb2FkAA8LaTMyX3N0b3JlMTYAEAlpMzJfc3RvcmUAEQtpNjRfc3RvcmUxNgASC2k2NF9zdG9yZTMyABMJaTY0X3N0b3JlABQJZjMyX3N0b3JlABUJZjY0X3N0b3JlABYK9gIXFgAgACABOgAAIABBAWogAUEIdjoAAAsUACAAIAEQACAAQQJqIAFBEHYQAAsWACAAIAGnEAEgAEEEaiABQiCIpxABCxMAIAAtAAAgAEEBai0AAEEIdHILEQAgABADIABBAmoQA0EQdHILEwAgABAErSAAQQRqEAStQiCGhAsNAEEAIAAQAEEALgEACw0AQQAgABAAQQAvAQALDQBBACAAEAFBACgCAAsOAEEAIACnEABBADIBAAsOAEEAIACnEABBADMBAAsOAEEAIACnEAFBADQCAAsOAEEAIACnEAFBADUCAAsNAEEAIAAQAkEAKQMACw4AQQAgALwQAUEAKgIACw4AQQAgAL0QAkEAKwMACw0AQQAgADsBAEEAEAMLDQBBACAANgIAQQAQBAsOAEEAIAA9AQBBABADrQsOAEEAIAA+AgBBABAErQsNAEEAIAA3AwBBABAFCw4AQQAgADgCAEEAEAS+Cw4AQQAgADkDAEEAEAW/Cw== +valid valid exports.wast:3 AGFzbQEAAAABBAFgAAADAgEABwUBAWEAAAoEAQIACw== +valid valid exports.wast:4 AGFzbQEAAAABBAFgAAADAgEABwkCAWEAAAFiAAAKBAECAAs= +valid valid exports.wast:5 AGFzbQEAAAABBAFgAAADAwIAAAcJAgFhAAABYgABCgcCAgALAgAL +valid valid exports.wast:8 AGFzbQEAAAABBAFgAAADAgEABw0DAWEAAAFiAAABYwAACgQBAgAL +valid valid exports.wast:9 AGFzbQEAAAABBQFgAX8AAwIBAAcJAgFhAAABYgAACgQBAgAL +valid valid exports.wast:16 AGFzbQEAAAABBgFgAX8BfwMCAQAHBQEBZQAACgoBCAAgAEEBag8L +valid valid exports.wast:28 AGFzbQEAAAABBQFgAAF/AwIBAAcNAwFhAAABYgAAAWMAAAoGAQQAQSoL +invalid invalid exports.wast:39 AGFzbQEAAAAHBQEBYQAA +invalid invalid exports.wast:43 AGFzbQEAAAABBAFgAAADAgEABwUBAWEAAQoEAQIACw== +invalid invalid exports.wast:47 AGFzbQEAAAABBQFgAX8AAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAABwUBAWEAAQ== +invalid invalid exports.wast:51 AGFzbQEAAAABBAFgAAADAgEABwkCAWEAAAFhAAAKBAECAAs= +invalid invalid exports.wast:55 AGFzbQEAAAABBAFgAAADAwIAAAcJAgFhAAABYQABCgcCAgALAgAL +invalid invalid exports.wast:59 AGFzbQEAAAABBAFgAAADAgEABgYBfwBBAAsHCQIBYQAAAWEDAAoEAQIACw== +invalid invalid exports.wast:63 AGFzbQEAAAABBAFgAAADAgEABAQBcAAABwkCAWEAAAFhAQAKBAECAAs= +invalid invalid exports.wast:67 AGFzbQEAAAABBAFgAAADAgEABQMBAAAHCQIBYQAAAWECAAoEAQIACw== +valid valid exports.wast:74 AGFzbQEAAAAGBgF/AEEACwcFAQFhAwA= +valid valid exports.wast:75 AGFzbQEAAAAGBgF/AEEACwcJAgFhAwABYgMA +valid valid exports.wast:76 AGFzbQEAAAAGCwJ/AEEAC38AQQALBwkCAWEDAAFiAwE= +valid valid exports.wast:85 AGFzbQEAAAAGBgF/AEEqCwcFAQFlAwA= +invalid invalid exports.wast:96 AGFzbQEAAAAHBQEBYQMA +invalid invalid exports.wast:100 AGFzbQEAAAAGBgF/AEEACwcFAQFhAwE= +invalid invalid exports.wast:104 AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAcFAQFhAwE= +invalid invalid exports.wast:108 AGFzbQEAAAAGBgF/AEEACwcJAgFhAwABYQMA +invalid invalid exports.wast:112 AGFzbQEAAAAGCwJ/AEEAC38AQQALBwkCAWEDAAFhAwE= +invalid invalid exports.wast:116 AGFzbQEAAAABBAFgAAADAgEABgYBfwBBAAsHCQIBYQMAAWEAAAoEAQIACw== +invalid invalid exports.wast:120 AGFzbQEAAAAEBAFwAAAGBgF/AEEACwcJAgFhAwABYQEA +invalid invalid exports.wast:124 AGFzbQEAAAAFAwEAAAYGAX8AQQALBwkCAWEDAAFhAgA= +valid valid exports.wast:131 AGFzbQEAAAAEBAFwAAAHBQEBYQEA +valid valid exports.wast:132 AGFzbQEAAAAEBAFwAAAHCQIBYQEAAWIBAA== +valid valid exports.wast:133 AGFzbQEAAAAEBwJwAABwAAAHCQIBYQEAAWIBAQ== +valid valid exports.wast:136 AGFzbQEAAAAEBQFwAQABBwUBAWEBAA== +invalid invalid exports.wast:151 AGFzbQEAAAAHBQEBYQEA +invalid invalid exports.wast:155 AGFzbQEAAAAEBAFwAAAHBQEBYQEB +invalid invalid exports.wast:159 AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKFAcFAQFhAQE= +invalid invalid exports.wast:163 AGFzbQEAAAAEBAFwAAAHCQIBYQEAAWEBAA== +invalid invalid exports.wast:167 AGFzbQEAAAAEBwJwAABwAAAHCQIBYQEAAWEBAQ== +invalid invalid exports.wast:171 AGFzbQEAAAABBAFgAAADAgEABAQBcAAABwkCAWEBAAFhAAAKBAECAAs= +invalid invalid exports.wast:175 AGFzbQEAAAAEBAFwAAAGBgF/AEEACwcJAgFhAQABYQMA +invalid invalid exports.wast:179 AGFzbQEAAAAEBAFwAAAFAwEAAAcJAgFhAQABYQIA +valid valid exports.wast:186 AGFzbQEAAAAFAwEAAAcFAQFhAgA= +valid valid exports.wast:187 AGFzbQEAAAAFAwEAAAcJAgFhAgABYgIA +valid valid exports.wast:192 AGFzbQEAAAAFBAEBAAEHBQEBYQIA +invalid invalid exports.wast:207 AGFzbQEAAAAHBQEBYQIA +invalid invalid exports.wast:211 AGFzbQEAAAAFAwEAAAcFAQFhAgE= +invalid invalid exports.wast:215 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAgcFAQFhAgE= +invalid invalid exports.wast:219 AGFzbQEAAAAFAwEAAAcJAgFhAgABYQIA +invalid invalid exports.wast:228 AGFzbQEAAAABBAFgAAADAgEABQMBAAAHCQIBYQIAAWEAAAoEAQIACw== +invalid invalid exports.wast:232 AGFzbQEAAAAFAwEAAAYGAX8AQQALBwkCAWECAAFhAwA= +invalid invalid exports.wast:236 AGFzbQEAAAAEBAFwAAAFAwEAAAcJAgFhAgABYQEA +valid valid f32.wast:5 AGFzbQEAAAABDAJgAn19AX1gAX0BfQMMCwAAAAABAAABAQEBB00LA2FkZAAAA3N1YgABA211bAACA2RpdgADBHNxcnQABANtaW4ABQNtYXgABgRjZWlsAAcFZmxvb3IACAV0cnVuYwAJB25lYXJlc3QACgpPCwcAIAAgAZILBwAgACABkwsHACAAIAGUCwcAIAAgAZULBQAgAJELBwAgACABlgsHACAAIAGXCwUAIACNCwUAIACOCwUAIACPCwUAIACQCw== +invalid invalid f32.wast:2523 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAACSCw== +invalid invalid f32.wast:2524 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAACVCw== +invalid invalid f32.wast:2525 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAACXCw== +invalid invalid f32.wast:2526 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAACWCw== +invalid invalid f32.wast:2527 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAACUCw== +invalid invalid f32.wast:2528 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAACTCw== +invalid invalid f32.wast:2529 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCNCw== +invalid invalid f32.wast:2530 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCOCw== +invalid invalid f32.wast:2531 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCQCw== +invalid invalid f32.wast:2532 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCRCw== +invalid invalid f32.wast:2533 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCPCw== +valid valid f32_bitwise.wast:4 AGFzbQEAAAABDAJgAX0BfWACfX0BfQMEAwAAAQcYAwNhYnMAAANuZWcAAQhjb3B5c2lnbgACChUDBQAgAIsLBQAgAIwLBwAgACABmAs= +invalid invalid f32_bitwise.wast:374 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAACYCw== +invalid invalid f32_bitwise.wast:375 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCLCw== +invalid invalid f32_bitwise.wast:376 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAQgCMCw== +valid valid f32_cmp.wast:4 AGFzbQEAAAABBwFgAn19AX8DBwYAAAAAAAAHHwYCZXEAAAJuZQABAmx0AAICbGUAAwJndAAEAmdlAAUKMQYHACAAIAFbCwcAIAAgAVwLBwAgACABXQsHACAAIAFfCwcAIAAgAV4LBwAgACABYAs= +invalid invalid f32_cmp.wast:2417 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAABbCw== +invalid invalid f32_cmp.wast:2418 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAABgCw== +invalid invalid f32_cmp.wast:2419 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAABeCw== +invalid invalid f32_cmp.wast:2420 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAABfCw== +invalid invalid f32_cmp.wast:2421 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAABdCw== +invalid invalid f32_cmp.wast:2422 AGFzbQEAAAABBQFgAAF9AwIBAAoQAQ4AQgBEAAAAAAAAAABcCw== +valid valid f64.wast:5 AGFzbQEAAAABDAJgAnx8AXxgAXwBfAMMCwAAAAABAAABAQEBB00LA2FkZAAAA3N1YgABA211bAACA2RpdgADBHNxcnQABANtaW4ABQNtYXgABgRjZWlsAAcFZmxvb3IACAV0cnVuYwAJB25lYXJlc3QACgpPCwcAIAAgAaALBwAgACABoQsHACAAIAGiCwcAIAAgAaMLBQAgAJ8LBwAgACABpAsHACAAIAGlCwUAIACbCwUAIACcCwUAIACdCwUAIACeCw== +invalid invalid f64.wast:2523 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAKAL +invalid invalid f64.wast:2524 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAKML +invalid invalid f64.wast:2525 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAKUL +invalid invalid f64.wast:2526 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAKQL +invalid invalid f64.wast:2527 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAKIL +invalid invalid f64.wast:2528 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAKEL +invalid invalid f64.wast:2529 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgCbCw== +invalid invalid f64.wast:2530 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgCcCw== +invalid invalid f64.wast:2531 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgCeCw== +invalid invalid f64.wast:2532 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgCfCw== +invalid invalid f64.wast:2533 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgCdCw== +valid valid f64_bitwise.wast:4 AGFzbQEAAAABDAJgAXwBfGACfHwBfAMEAwAAAQcYAwNhYnMAAANuZWcAAQhjb3B5c2lnbgACChUDBQAgAJkLBQAgAJoLBwAgACABpgs= +invalid invalid f64_bitwise.wast:374 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAKYL +invalid invalid f64_bitwise.wast:375 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgCZCw== +invalid invalid f64_bitwise.wast:376 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgCaCw== +valid valid f64_cmp.wast:4 AGFzbQEAAAABBwFgAnx8AX8DBwYAAAAAAAAHHwYCZXEAAAJuZQABAmx0AAICbGUAAwJndAAEAmdlAAUKMQYHACAAIAFhCwcAIAAgAWILBwAgACABYwsHACAAIAFlCwcAIAAgAWQLBwAgACABZgs= +invalid invalid f64_cmp.wast:2417 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAGEL +invalid invalid f64_cmp.wast:2418 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAGYL +invalid invalid f64_cmp.wast:2419 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAGQL +invalid invalid f64_cmp.wast:2420 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAGUL +invalid invalid f64_cmp.wast:2421 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAGML +invalid invalid f64_cmp.wast:2422 AGFzbQEAAAABBQFgAAF8AwIBAAoMAQoAQgBDAAAAAGIL +valid valid fac.wast:1 AGFzbQEAAAABGgRgAX4BfmABfgJ+fmACfn4Dfn5+YAJ+fgF+AwkIAAAAAAABAgAHSwYHZmFjLXJlYwAADWZhYy1yZWMtbmFtZWQAAQhmYWMtaXRlcgACDmZhYy1pdGVyLW5hbWVkAAMHZmFjLW9wdAAEB2ZhYy1zc2EABwrrAQgXACAAQgBRBH5CAQUgACAAQgF9EAB+CwsXACAAQgBRBH5CAQUgACAAQgF9EAF+CwsvAQJ+IAAhAUIBIQICQANAIAFCAFEEQAwCBSABIAJ+IQIgAUIBfSEBCwwACwsgAgsvAQJ+IAAhAUIBIQICQANAIAFCAFEEQAwCBSABIAJ+IQIgAUIBfSEBCwwACwsgAgssAQF+QgEhAQJAIABCAlMNAANAIAEgAH4hASAAQn98IQAgAEIBVQ0ACwsgAQsGACAAIAALCAAgACABIAALHABCASAAAwMQBhAGfhAGQgF9EAVCAFYNABoPCws= +valid valid float_exprs.wast:6 AGFzbQEAAAABCAFgA3x8fAF8AwIBAAcWARJmNjQubm9fY29udHJhY3Rpb24AAAoMAQoAIAAgAaIgAqAL +valid valid float_exprs.wast:19 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBxsCCmYzMi5ub19mbWEAAApmNjQubm9fZm1hAAEKFwIKACAAIAGUIAKSCwoAIAAgAaIgAqAL +valid valid float_exprs.wast:40 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfYWRkX3plcm8AABRmNjQubm9fZm9sZF9hZGRfemVybwABChsCCgAgAEMAAAAAkgsOACAARAAAAAAAAAAAoAs= +valid valid float_exprs.wast:54 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfemVyb19zdWIAABRmNjQubm9fZm9sZF96ZXJvX3N1YgABChsCCgBDAAAAACAAkwsOAEQAAAAAAAAAACAAoQs= +valid valid float_exprs.wast:68 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfc3ViX3plcm8AABRmNjQubm9fZm9sZF9zdWJfemVybwABChsCCgAgAEMAAAAAkwsOACAARAAAAAAAAAAAoQs= +valid valid float_exprs.wast:80 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbXVsX3plcm8AABRmNjQubm9fZm9sZF9tdWxfemVybwABChsCCgAgAEMAAAAAlAsOACAARAAAAAAAAAAAogs= +valid valid float_exprs.wast:99 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfbXVsX29uZQAAE2Y2NC5ub19mb2xkX211bF9vbmUAAQobAgoAIABDAACAP5QLDgAgAEQAAAAAAADwP6IL +valid valid float_exprs.wast:111 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfemVyb19kaXYAABRmNjQubm9fZm9sZF96ZXJvX2RpdgABChsCCgBDAAAAACAAlQsOAEQAAAAAAAAAACAAows= +valid valid float_exprs.wast:129 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfZGl2X29uZQAAE2Y2NC5ub19mb2xkX2Rpdl9vbmUAAQobAgoAIABDAACAP5ULDgAgAEQAAAAAAADwP6ML +valid valid float_exprs.wast:141 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X25lZzEAABRmNjQubm9fZm9sZF9kaXZfbmVnMQABChsCCgAgAEMAAIC/lQsOACAARAAAAAAAAPC/ows= +valid valid float_exprs.wast:153 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbmVnMF9zdWIAABRmNjQubm9fZm9sZF9uZWcwX3N1YgABChsCCgBDAAAAgCAAkwsOAEQAAAAAAAAAgCAAoQs= +valid valid float_exprs.wast:165 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbmVnMV9tdWwAABRmNjQubm9fZm9sZF9uZWcxX211bAABChsCCgBDAACAvyAAlAsOAEQAAAAAAADwvyAAogs= +valid valid float_exprs.wast:177 AGFzbQEAAAABCwJgAX0Bf2ABfAF/AwMCAAEHLQITZjMyLm5vX2ZvbGRfZXFfc2VsZgAAE2Y2NC5ub19mb2xkX2VxX3NlbGYAAQoRAgcAIAAgAFsLBwAgACAAYQs= +valid valid float_exprs.wast:189 AGFzbQEAAAABCwJgAX0Bf2ABfAF/AwMCAAEHLQITZjMyLm5vX2ZvbGRfbmVfc2VsZgAAE2Y2NC5ub19mb2xkX25lX3NlbGYAAQoRAgcAIAAgAFwLBwAgACAAYgs= +valid valid float_exprs.wast:201 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfc3ViX3NlbGYAABRmNjQubm9fZm9sZF9zdWJfc2VsZgABChECBwAgACAAkwsHACAAIAChCw== +valid valid float_exprs.wast:215 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X3NlbGYAABRmNjQubm9fZm9sZF9kaXZfc2VsZgABChECBwAgACAAlQsHACAAIACjCw== +valid valid float_exprs.wast:233 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHKQIRZjMyLm5vX2ZvbGRfZGl2XzMAABFmNjQubm9fZm9sZF9kaXZfMwABChsCCgAgAEMAAEBAlQsOACAARAAAAAAAAAhAows= +valid valid float_exprs.wast:253 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABByECDWYzMi5ub19mYWN0b3IAAA1mNjQubm9fZmFjdG9yAAEKHQINACAAIAKUIAEgApSSCw0AIAAgAqIgASACoqAL +valid valid float_exprs.wast:273 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBykCEWYzMi5ub19kaXN0cmlidXRlAAARZjY0Lm5vX2Rpc3RyaWJ1dGUAAQoXAgoAIAAgAZIgApQLCgAgACABoCACogs= +valid valid float_exprs.wast:293 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBzMCFmYzMi5ub19yZWdyb3VwX2Rpdl9tdWwAABZmNjQubm9fcmVncm91cF9kaXZfbXVsAAEKFwIKACAAIAEgApWUCwoAIAAgASACo6IL +valid valid float_exprs.wast:313 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBzMCFmYzMi5ub19yZWdyb3VwX211bF9kaXYAABZmNjQubm9fcmVncm91cF9tdWxfZGl2AAEKFwIKACAAIAGUIAKVCwoAIAAgAaIgAqML +valid valid float_exprs.wast:333 AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHMwIWZjMyLm5vX3JlYXNzb2NpYXRlX2FkZAAAFmY2NC5ub19yZWFzc29jaWF0ZV9hZGQAAQodAg0AIAAgAZIgApIgA5ILDQAgACABoCACoCADoAs= +valid valid float_exprs.wast:353 AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHMwIWZjMyLm5vX3JlYXNzb2NpYXRlX211bAAAFmY2NC5ub19yZWFzc29jaWF0ZV9tdWwAAQodAg0AIAAgAZQgApQgA5QLDQAgACABoiACoiADogs= +valid valid float_exprs.wast:373 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHKQIRZjMyLm5vX2ZvbGRfZGl2XzAAABFmNjQubm9fZm9sZF9kaXZfMAABChsCCgAgAEMAAAAAlQsOACAARAAAAAAAAAAAows= +valid valid float_exprs.wast:399 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X25lZzAAABRmNjQubm9fZm9sZF9kaXZfbmVnMAABChsCCgAgAEMAAACAlQsOACAARAAAAAAAAACAows= +valid valid float_exprs.wast:425 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcvAhRmMzIubm9fZm9sZF90b19oeXBvdAAAFGY2NC5ub19mb2xkX3RvX2h5cG90AAEKHwIOACAAIACUIAEgAZSSkQsOACAAIACiIAEgAaKgnws= +valid valid float_exprs.wast:447 AGFzbQEAAAABBgFgAX0BfQMCAQAHIQEdZjMyLm5vX2FwcHJveGltYXRlX3JlY2lwcm9jYWwAAAoMAQoAQwAAgD8gAJUL +valid valid float_exprs.wast:460 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHRAIiZjMyLm5vX2FwcHJveGltYXRlX3JlY2lwcm9jYWxfc3FydAAAG2Y2NC5ub19mdXNlX3JlY2lwcm9jYWxfc3FydAABCh0CCwBDAACAPyAAkZULDwBEAAAAAAAA8D8gAJ+jCw== +valid valid float_exprs.wast:481 AGFzbQEAAAABBgFgAX0BfQMCAQAHJgEiZjMyLm5vX2FwcHJveGltYXRlX3NxcnRfcmVjaXByb2NhbAAACg0BCwBDAACAPyAAlZEL +valid valid float_exprs.wast:494 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdRBBFpMzIubm9fZm9sZF9mMzJfcwAAEWkzMi5ub19mb2xkX2YzMl91AAERaTY0Lm5vX2ZvbGRfZjY0X3MAAhFpNjQubm9fZm9sZF9mNjRfdQADCh0EBgAgALKoCwYAIACzqQsGACAAubALBgAgALqxCw== +valid valid float_exprs.wast:523 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9hZGRfc3ViAAATZjY0Lm5vX2ZvbGRfYWRkX3N1YgABChcCCgAgACABkiABkwsKACAAIAGgIAGhCw== +valid valid float_exprs.wast:544 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9zdWJfYWRkAAATZjY0Lm5vX2ZvbGRfc3ViX2FkZAABChcCCgAgACABkyABkgsKACAAIAGhIAGgCw== +valid valid float_exprs.wast:565 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9tdWxfZGl2AAATZjY0Lm5vX2ZvbGRfbXVsX2RpdgABChcCCgAgACABlCABlQsKACAAIAGiIAGjCw== +valid valid float_exprs.wast:586 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9kaXZfbXVsAAATZjY0Lm5vX2ZvbGRfZGl2X211bAABChcCCgAgACABlSABlAsKACAAIAGjIAGiCw== +valid valid float_exprs.wast:607 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHMQIVZjMyLm5vX2ZvbGRfZGl2Ml9tdWwyAAAVZjY0Lm5vX2ZvbGRfZGl2Ml9tdWwyAAEKKwIQACAAQwAAAECVQwAAAECUCxgAIABEAAAAAAAAAECjRAAAAAAAAABAogs= +valid valid float_exprs.wast:619 AGFzbQEAAAABBgFgAXwBfAMCAQAHGgEWbm9fZm9sZF9kZW1vdGVfcHJvbW90ZQAACggBBgAgALa7Cw== +valid valid float_exprs.wast:633 AGFzbQEAAAABBgFgAX0BfQMCAQAHGgEWbm9fZm9sZF9wcm9tb3RlX2RlbW90ZQAACggBBgAgALu2Cw== +valid valid float_exprs.wast:655 AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfYWRkAAAcbm9fZGVtb3RlX21peGVkX2FkZF9jb21tdXRlZAABChUCCQAgACABu6C2CwkAIAC7IAGgtgs= +valid valid float_exprs.wast:677 AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfc3ViAAAcbm9fZGVtb3RlX21peGVkX3N1Yl9jb21tdXRlZAABChUCCQAgACABu6G2CwkAIAC7IAGhtgs= +valid valid float_exprs.wast:699 AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfbXVsAAAcbm9fZGVtb3RlX21peGVkX211bF9jb21tdXRlZAABChUCCQAgACABu6K2CwkAIAC7IAGitgs= +valid valid float_exprs.wast:721 AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfZGl2AAAcbm9fZGVtb3RlX21peGVkX2Rpdl9jb21tdXRlZAABChUCCQAgACABu6O2CwkAIAC7IAGjtgs= +valid valid float_exprs.wast:742 AGFzbQEAAAABCwJgAX0BfWABfAF8AxEQAAAAAAEBAQEAAAAAAQEBAQfBBBAhZjMyLmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAAhZjMyLmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAEhZjMyLmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAIhZjMyLmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAMhZjY0LmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAQhZjY0LmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAUhZjY0LmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAYhZjY0LmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAchZjMyLmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAghZjMyLmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAkhZjMyLmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAohZjMyLmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAshZjY0Lmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAwhZjY0Lmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAA0hZjY0Lmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF91AA4hZjY0Lmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF91AA8KcRAGACAAqLILBgAgAKmyCwYAIACoswsGACAAqbMLBgAgAKq3CwYAIACrtwsGACAAqrgLBgAgAKu4CwYAIACutAsGACAAr7QLBgAgAK61CwYAIACvtQsGACAAsLkLBgAgALG5CwYAIACwugsGACAAsboL +valid valid float_exprs.wast:817 AGFzbQEAAAABCwJgAn99AGABfwF9AwQDAAABBQQBAQEBBxYDBGluaXQAAANydW4AAQVjaGVjawACCjkDCQAgACABOAIACyUBAX8CQANAIAIgAioCACABlTgCACACQQRqIQIgAiAASQ0ACwsLBwAgACoCAAs= +valid valid float_exprs.wast:852 AGFzbQEAAAABCwJgAn98AGABfwF8AwQDAAABBQQBAQEBBxYDBGluaXQAAANydW4AAQVjaGVjawACCjkDCQAgACABOQMACyUBAX8CQANAIAIgAisDACABozkDACACQQhqIQIgAiAASQ0ACwsLBwAgACsDAAs= +valid valid float_exprs.wast:889 AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQdRCAdmMzIudWx0AAAHZjMyLnVsZQABB2YzMi51Z3QAAgdmMzIudWdlAAMHZjY0LnVsdAAEB2Y2NC51bGUABQdmNjQudWd0AAYHZjY0LnVnZQAHCkkICAAgACABYEULCAAgACABXkULCAAgACABX0ULCAAgACABXUULCAAgACABZkULCAAgACABZEULCAAgACABZUULCAAgACABY0UL +valid valid float_exprs.wast:936 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDCQgAAAAAAQEBAQfBAQgVZjMyLm5vX2ZvbGRfbHRfc2VsZWN0AAAVZjMyLm5vX2ZvbGRfbGVfc2VsZWN0AAEVZjMyLm5vX2ZvbGRfZ3Rfc2VsZWN0AAIVZjMyLm5vX2ZvbGRfZ2Vfc2VsZWN0AAMVZjY0Lm5vX2ZvbGRfbHRfc2VsZWN0AAQVZjY0Lm5vX2ZvbGRfbGVfc2VsZWN0AAUVZjY0Lm5vX2ZvbGRfZ3Rfc2VsZWN0AAYVZjY0Lm5vX2ZvbGRfZ2Vfc2VsZWN0AAcKaQgMACAAIAEgACABXRsLDAAgACABIAAgAV8bCwwAIAAgASAAIAFeGwsMACAAIAEgACABYBsLDAAgACABIAAgAWMbCwwAIAAgASAAIAFlGwsMACAAIAEgACABZBsLDAAgACABIAAgAWYbCw== +valid valid float_exprs.wast:983 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDCQgAAAAAAQEBAQehAQgRZjMyLm5vX2ZvbGRfbHRfaWYAABFmMzIubm9fZm9sZF9sZV9pZgABEWYzMi5ub19mb2xkX2d0X2lmAAIRZjMyLm5vX2ZvbGRfZ2VfaWYAAxFmNjQubm9fZm9sZF9sdF9pZgAEEWY2NC5ub19mb2xkX2xlX2lmAAURZjY0Lm5vX2ZvbGRfZ3RfaWYABhFmNjQubm9fZm9sZF9nZV9pZgAHCoEBCA8AIAAgAV0EfSAABSABCwsPACAAIAFfBH0gAAUgAQsLDwAgACABXgR9IAAFIAELCw8AIAAgAWAEfSAABSABCwsPACAAIAFjBHwgAAUgAQsLDwAgACABZQR8IAAFIAELCw8AIAAgAWQEfCAABSABCwsPACAAIAFmBHwgAAUgAQsL +valid valid float_exprs.wast:1062 AGFzbQEAAAABCwJgAX0BfWABfAF8AwkIAAAAAAEBAQEH+QEIHGYzMi5ub19mb2xkX2x0X3NlbGVjdF90b19hYnMAABxmMzIubm9fZm9sZF9sZV9zZWxlY3RfdG9fYWJzAAEcZjMyLm5vX2ZvbGRfZ3Rfc2VsZWN0X3RvX2FicwACHGYzMi5ub19mb2xkX2dlX3NlbGVjdF90b19hYnMAAxxmNjQubm9fZm9sZF9sdF9zZWxlY3RfdG9fYWJzAAQcZjY0Lm5vX2ZvbGRfbGVfc2VsZWN0X3RvX2FicwAFHGY2NC5ub19mb2xkX2d0X3NlbGVjdF90b19hYnMABhxmNjQubm9fZm9sZF9nZV9zZWxlY3RfdG9fYWJzAAcKmQEIEAAgAIwgACAAQwAAAABdGwsQACAAjCAAIABDAAAAgF8bCxAAIAAgAIwgAEMAAACAXhsLEAAgACAAjCAAQwAAAABgGwsUACAAmiAAIABEAAAAAAAAAABjGwsUACAAmiAAIABEAAAAAAAAAIBlGwsUACAAIACaIABEAAAAAAAAAIBkGwsUACAAIACaIABEAAAAAAAAAABmGws= +valid valid float_exprs.wast:1109 AGFzbQEAAAABCwJgAX0BfWABfAF8AwkIAAAAAAEBAQEH2QEIGGYzMi5ub19mb2xkX2x0X2lmX3RvX2FicwAAGGYzMi5ub19mb2xkX2xlX2lmX3RvX2FicwABGGYzMi5ub19mb2xkX2d0X2lmX3RvX2FicwACGGYzMi5ub19mb2xkX2dlX2lmX3RvX2FicwADGGY2NC5ub19mb2xkX2x0X2lmX3RvX2FicwAEGGY2NC5ub19mb2xkX2xlX2lmX3RvX2FicwAFGGY2NC5ub19mb2xkX2d0X2lmX3RvX2FicwAGGGY2NC5ub19mb2xkX2dlX2lmX3RvX2FicwAHCrEBCBMAIABDAAAAAF0EfSAAjAUgAAsLEwAgAEMAAACAXwR9IACMBSAACwsTACAAQwAAAIBeBH0gAAUgAIwLCxMAIABDAAAAAGAEfSAABSAAjAsLFwAgAEQAAAAAAAAAAGMEfCAAmgUgAAsLFwAgAEQAAAAAAAAAgGUEfCAAmgUgAAsLFwAgAEQAAAAAAAAAgGQEfCAABSAAmgsLFwAgAEQAAAAAAAAAAGYEfCAABSAAmgsL +valid valid float_exprs.wast:1189 AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBzcCGGYzMi5pbmNvcnJlY3RfY29ycmVjdGlvbgAAGGY2NC5pbmNvcnJlY3RfY29ycmVjdGlvbgABCkUCGQBDvp+qP0PNzJw/kkO+n6o/k0PNzJw/kwspAESHFtnO91P1P0SamZmZmZnzP6BEhxbZzvdT9T+hRJqZmZmZmfM/oQs= +valid valid float_exprs.wast:1204 AGFzbQEAAAABBQFgAAF9AwIBAAcNAQljYWxjdWxhdGUAAApKAUgBBX1DAEAcQyEAQ1VVUEMhAUMVx+M/IQIgAYwgAJQgACAClCABk5UhAyABjCAAlCAAIAKUIAGTlSEEAkAgAyAEWw0AAAsgBAs= +valid valid float_exprs.wast:1223 AGFzbQEAAAABBQFgAAF8AwIBAAcNAQljYWxjdWxhdGUAAApWAVQBBXxEAAAAAACIY0AhAERLBquqqgpqQCEBRJbQ46riePw/IQIgAZogAKIgACACoiABoaMhAyABmiAAoiAAIAKiIAGhoyEEAkAgAyAEYQ0AAAsgBAs= +valid valid float_exprs.wast:1245 AGFzbQEAAAABBgFgAX0BfQMCAQAHEAEMbGx2bV9wcjI2NzQ2AAAKEgEQAEMAAAAAQwAAAIAgAJOTCw== +valid valid float_exprs.wast:1256 AGFzbQEAAAABBgFgAX8BfQMCAQAHEAEMbGx2bV9wcjI3MTUzAAAKFAESACAAQf////8AcbJDAAAAy5IL +valid valid float_exprs.wast:1267 AGFzbQEAAAABBwFgAn9/AX0DAgEABxABDGxsdm1fcHIyNzAzNgAAChcBFQAgAEHL/4d0crIgAUGAgJAHcbKSCw== +valid valid float_exprs.wast:1284 AGFzbQEAAAABFwNgBHx8fHwBfGADfHx8AXxgA319fQF9AwQDAAECByIDCHRoZXBhc3QwAAAIdGhlcGFzdDEAAQh0aGVwYXN0MgACCiUDDQAgACABoiACIAOiowsKACAAIAGiIAKhCwoAIAAgAZQgApQL +valid valid float_exprs.wast:1305 AGFzbQEAAAABBgFgAX0BfQMCAQAHCwEHaW52ZXJzZQAACgwBCgBDAACAPyAAlQs= +valid valid float_exprs.wast:1316 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHJwIQZjMyX3NxcnRfbWludXNfMgAAEGY2NF9zcXJ0X21pbnVzXzIAAQodAgsAIACRQwAAAECTCw8AIACfRAAAAAAAAABAoQs= +valid valid float_exprs.wast:1331 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2ZvbGRfcmVjaXBfcmVjaXAAABdmNjQubm9fZm9sZF9yZWNpcF9yZWNpcAABCisCEABDAACAP0MAAIA/IACVlQsYAEQAAAAAAADwP0QAAAAAAADwPyAAo6ML +valid valid float_exprs.wast:1363 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc7AhpmMzIubm9fYWxnZWJyYWljX2ZhY3RvcmluZwAAGmY2NC5ub19hbGdlYnJhaWNfZmFjdG9yaW5nAAEKHQINACAAIAGSIAAgAZOUCw0AIAAgAaAgACABoaIL +valid valid float_exprs.wast:1387 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc7AhpmMzIubm9fYWxnZWJyYWljX2ZhY3RvcmluZwAAGmY2NC5ub19hbGdlYnJhaWNfZmFjdG9yaW5nAAEKHQINACAAIACUIAEgAZSTCw0AIAAgAKIgASABoqEL +valid valid float_exprs.wast:1412 AGFzbQEAAAABDAJgA39/fwBgAX8BfQMDAgABBQQBAQEBByACEWYzMi5zaW1wbGVfeDRfc3VtAAAIZjMyLmxvYWQAAQpuAmQBCH0gACoCACEDIAAqAgQhBCAAKgIIIQUgACoCDCEGIAEqAgAhByABKgIEIQggASoCCCEJIAEqAgwhCiACIAMgB5I4AgAgAiAEIAiSOAIEIAIgBSAJkjgCCCACIAYgCpI4AgwLBwAgACoCAAsLNgEAQQALMAEAAAABAACAAQAAAAEAAIABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== +valid valid float_exprs.wast:1451 AGFzbQEAAAABDAJgA39/fwBgAX8BfAMDAgABBQQBAQEBByACEWY2NC5zaW1wbGVfeDRfc3VtAAAIZjY0LmxvYWQAAQpuAmQBCHwgACsDACEDIAArAwghBCAAKwMQIQUgACsDGCEGIAErAwAhByABKwMIIQggASsDECEJIAErAxghCiACIAMgB6A5AwAgAiAEIAigOQMIIAIgBSAJoDkDECACIAYgCqA5AxgLBwAgACsDAAsLZgEAQQALYAEAAAAAAAAAAQAAAAAAAIABAAAAAAAAAAEAAAAAAACAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== +valid valid float_exprs.wast:1493 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 +valid valid float_exprs.wast:1587 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== +valid valid float_exprs.wast:1640 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9uZWdfc3ViAAATZjY0Lm5vX2ZvbGRfbmVnX3N1YgABChMCCAAgACABk4wLCAAgACABoZoL +valid valid float_exprs.wast:1660 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9uZWdfYWRkAAATZjY0Lm5vX2ZvbGRfbmVnX2FkZAABChMCCAAgACABkowLCAAgACABoJoL +valid valid float_exprs.wast:1680 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc1AhdmMzIubm9fZm9sZF9hZGRfbmVnX25lZwAAF2Y2NC5ub19mb2xkX2FkZF9uZWdfbmVnAAEKFQIJACAAjCABjJILCQAgAJogAZqgCw== +valid valid float_exprs.wast:1700 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfYWRkX25lZwAAE2Y2NC5ub19mb2xkX2FkZF9uZWcAAQoTAggAIACMIACSCwgAIACaIACgCw== +valid valid float_exprs.wast:1720 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHMwIWZjMyLm5vX2ZvbGRfNnhfdmlhX2FkZAAAFmY2NC5ub19mb2xkXzZ4X3ZpYV9hZGQAAQopAhMAIAAgAJIgAJIgAJIgAJIgAJILEwAgACAAoCAAoCAAoCAAoCAAoAs= +valid valid float_exprs.wast:1749 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBy0CE2YzMi5ub19mb2xkX2Rpdl9kaXYAABNmNjQubm9fZm9sZF9kaXZfZGl2AAEKFwIKACAAIAGVIAKVCwoAIAAgAaMgAqML +valid valid float_exprs.wast:1773 AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbXVsX2RpdnMAABRmNjQubm9fZm9sZF9tdWxfZGl2cwABCh0CDQAgACABlSACIAOVlAsNACAAIAGjIAIgA6OiCw== +valid valid float_exprs.wast:1795 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBy8CFGYzMi5ub19mb2xkX2FkZF9kaXZzAAAUZjY0Lm5vX2ZvbGRfYWRkX2RpdnMAAQodAg0AIAAgApUgASAClZILDQAgACACoyABIAKjoAs= +valid valid float_exprs.wast:1817 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2ZvbGRfc3FydF9zcXVhcmUAABdmNjQubm9fZm9sZF9zcXJ0X3NxdWFyZQABChMCCAAgACAAlJELCAAgACAAop8L +valid valid float_exprs.wast:1839 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcxAhVmMzIubm9fZm9sZF9tdWxfc3FydHMAABVmNjQubm9fZm9sZF9tdWxfc3FydHMAAQoVAgkAIACRIAGRlAsJACAAnyABn6IL +valid valid float_exprs.wast:1861 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcxAhVmMzIubm9fZm9sZF9kaXZfc3FydHMAABVmNjQubm9fZm9sZF9kaXZfc3FydHMAAQoVAgkAIACRIAGRlQsJACAAnyABn6ML +valid valid float_exprs.wast:1883 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc3AhhmMzIubm9fZm9sZF9tdWxfc3FydF9kaXYAABhmNjQubm9fZm9sZF9tdWxfc3FydF9kaXYAAQoZAgsAIAAgAZGUIAGVCwsAIAAgAZ+iIAGjCw== +valid valid float_exprs.wast:1906 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABB00CI2YzMi5ub19mbHVzaF9pbnRlcm1lZGlhdGVfc3Vibm9ybWFsAAAjZjY0Lm5vX2ZsdXNoX2ludGVybWVkaWF0ZV9zdWJub3JtYWwAAQoXAgoAIAAgAZQgApQLCgAgACABoiACogs= +valid valid float_exprs.wast:1920 AGFzbQEAAAABEwNgAn19AX9gAnx8AX9gAnx9AX0DCAcAAAABAQECB38HD2YzMi5yZWNvZGluZ19lcQAAD2YzMi5yZWNvZGluZ19sZQABD2YzMi5yZWNvZGluZ19sdAACD2Y2NC5yZWNvZGluZ19lcQADD2Y2NC5yZWNvZGluZ19sZQAED2Y2NC5yZWNvZGluZ19sdAAFD3JlY29kaW5nX2RlbW90ZQAGCkwHCgAgACABlCAAWwsKACAAIAGUIABfCwoAIAAgAZQgAF0LCgAgACABoiAAYQsKACAAIAGiIABlCwoAIAAgAaIgAGMLCAAgALYgAZQL +valid valid float_exprs.wast:1964 AGFzbQEAAAABDwJgA319fQF/YAN8fHwBfwMDAgABB0ECHWYzMi5ub19leHRlbmRlZF9wcmVjaXNpb25fZGl2AAAdZjY0Lm5vX2V4dGVuZGVkX3ByZWNpc2lvbl9kaXYAAQoXAgoAIAAgAZUgAlsLCgAgACABoyACYQs= +valid valid float_exprs.wast:1980 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2Rpc3RyaWJ1dGVfZXhhY3QAABdmNjQubm9fZGlzdHJpYnV0ZV9leGFjdAABCjECEwBDAAAAwSAAlEMAAABBIACUkgsbAEQAAAAAAAAgwCAAokQAAAAAAAAgQCAAoqAL +valid valid float_exprs.wast:1994 AGFzbQEAAAABKQZgAX0BfWAEfX19fQF9YAN9fX0BfWABfAF8YAR8fHx8AXxgA3x8fAF8AwsKAAECAgEDBAUFBAe1AQoIZjMyLnNxcnQAAA9mMzIueGtjZF9zcXJ0XzIAAQ9mMzIueGtjZF9zcXJ0XzMAAg9mMzIueGtjZF9zcXJ0XzUAAxZmMzIueGtjZF9iZXR0ZXJfc3FydF81AAQIZjY0LnNxcnQABQ9mNjQueGtjZF9zcXJ0XzIABg9mNjQueGtjZF9zcXJ0XzMABw9mNjQueGtjZF9zcXJ0XzUACBZmNjQueGtjZF9iZXR0ZXJfc3FydF81AAkKiQEKBQAgAJELEAAgACABlSACIAMgApOVkgsKACAAIAGUIAKVCw0AIAAgAZUgAiAAlZILEwAgACABIAKUkiADIAEgApSTlQsFACAAnwsQACAAIAGjIAIgAyACoaOgCwoAIAAgAaIgAqMLDQAgACABoyACIACjoAsTACAAIAEgAqKgIAMgASACoqGjCw== +valid valid float_exprs.wast:2045 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcpAhFmMzIuY29tcHV0ZV9yYWRpeAAAEWY2NC5jb21wdXRlX3JhZGl4AAEKnQECQwADQCAAIACSIgBDAACAP5IgAJNDAACAv5JDAAAAAFsNAAsDQCAAIAFDAACAP5IiAZIgAJMgAZNDAAAAAFwNAAsgAQtXAANAIAAgAKAiAEQAAAAAAADwP6AgAKFEAAAAAAAA8L+gRAAAAAAAAAAAYQ0ACwNAIAAgAUQAAAAAAADwP6AiAaAgAKEgAaFEAAAAAAAAAABiDQALIAEL +valid valid float_exprs.wast:2129 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc3AhhmMzIubm9fZm9sZF9zdWIxX211bF9hZGQAABhmNjQubm9fZm9sZF9zdWIxX211bF9hZGQAAQonAhAAIABDAACAP5MgAZQgAZILFAAgAEQAAAAAAADwP6EgAaIgAaAL +valid valid float_exprs.wast:2143 AGFzbQEAAAABDwJgA319fQF/YAN8fHwBfwMFBAAAAQEHiQEEH2YzMi5ub19mb2xkX2FkZF9sZV9tb25vdG9uaWNpdHkAAB9mMzIubm9fZm9sZF9hZGRfZ2VfbW9ub3RvbmljaXR5AAEfZjY0Lm5vX2ZvbGRfYWRkX2xlX21vbm90b25pY2l0eQACH2Y2NC5ub19mb2xkX2FkZF9nZV9tb25vdG9uaWNpdHkAAwo5BA0AIAAgApIgASACkl8LDQAgACACkiABIAKSYAsNACAAIAKgIAEgAqBlCw0AIAAgAqAgASACoGYL +valid valid float_exprs.wast:2164 AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQdpCApmMzIubm90X2x0AAAKZjMyLm5vdF9sZQABCmYzMi5ub3RfZ3QAAgpmMzIubm90X2dlAAMKZjY0Lm5vdF9sdAAECmY2NC5ub3RfbGUABQpmNjQubm90X2d0AAYKZjY0Lm5vdF9nZQAHCkkICAAgACABXUULCAAgACABX0ULCAAgACABXkULCAAgACABYEULCAAgACABY0ULCAAgACABZUULCAAgACABZEULCAAgACABZkUL +valid valid float_exprs.wast:2203 AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBx0CC2YzMi5lcHNpbG9uAAALZjY0LmVwc2lsb24AAQpVAh8AQwAAgD9DAABAQEMAAIBAQwAAQECVQwAAgD+TlJMLMwBEAAAAAAAA8D9EAAAAAAAACEBEAAAAAAAAEEBEAAAAAAAACECjRAAAAAAAAPA/oaKhCw== +valid valid float_exprs.wast:2218 AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBx0CC2YzMi5lcHNpbG9uAAALZjY0LmVwc2lsb24AAQpnAioBAn1DAACAPyEAA0AgACIBQwAAAD+UIgBDAACAP5JDAACAP14NAAsgAQs6AQJ8RAAAAAAAAPA/IQADQCAAIgFEAAAAAAAA4D+iIgBEAAAAAAAA8D+gRAAAAAAAAPA/ZA0ACyABCw== +valid valid float_exprs.wast:2272 AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQe5AQgUZjMyLm5vX3RyaWNob3RvbXlfbHQAABRmMzIubm9fdHJpY2hvdG9teV9sZQABFGYzMi5ub190cmljaG90b215X2d0AAIUZjMyLm5vX3RyaWNob3RvbXlfZ2UAAxRmNjQubm9fdHJpY2hvdG9teV9sdAAEFGY2NC5ub190cmljaG90b215X2xlAAUUZjY0Lm5vX3RyaWNob3RvbXlfZ3QABhRmNjQubm9fdHJpY2hvdG9teV9nZQAHCnEIDQAgACABXSAAIAFgcgsNACAAIAFfIAAgAV5yCw0AIAAgAV4gACABX3ILDQAgACABYCAAIAFdcgsNACAAIAFjIAAgAWZyCw0AIAAgAWUgACABZHILDQAgACABZCAAIAFlcgsNACAAIAFmIAAgAWNyCw== +valid valid float_exprs.wast:2304 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== +valid valid float_exprs.wast:2420 AGFzbQEAAAABDQFgCHx8fHx8fHx8AXwDAwIAAAcyAhNkb3RfcHJvZHVjdF9leGFtcGxlAAAYd2l0aF9iaW5hcnlfc3VtX2NvbGxhcHNlAAEKNQIZACAAIASiIAEgBaKgIAIgBqKgIAMgB6KgCxkAIAAgBKIgASAFoqAgAiAGoiADIAeioKAL +valid valid float_exprs.wast:2454 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcnAhBmMzIuY29udHJhY3QyZm1hAAAQZjY0LmNvbnRyYWN0MmZtYQABCh8CDgAgACAAlCABIAGUk5ELDgAgACAAoiABIAGioZ8L +valid valid float_exprs.wast:2475 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBz8CHGYzMi5kaXZpc2lvbl9ieV9zbWFsbF9udW1iZXIAABxmNjQuZGl2aXNpb25fYnlfc21hbGxfbnVtYmVyAAEKFwIKACAAIAEgApWTCwoAIAAgASACo6EL +valid valid float_exprs.wast:2490 AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBycCEGYzMi5nb2xkZW5fcmF0aW8AABBmNjQuZ29sZGVuX3JhdGlvAAEKGQILACAAIAEgApGSlAsLACAAIAEgAp+gogs= +valid valid float_exprs.wast:2503 AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHJwIQZjMyLnNpbHZlcl9tZWFucwAAEGY2NC5zaWx2ZXJfbWVhbnMAAQo5AhcAQwAAAD8gACAAIACUQwAAgECSkZKUCx8ARAAAAAAAAOA/IAAgACAAokQAAAAAAAAQQKCfoKIL +valid valid float_exprs.wast:2532 AGFzbQEAAAABBwFgAnx8AX8DAgEABw4BCnBvaW50X2ZvdXIAAAoTAREAIAAgAaNEmpmZmZmZ2T9jCw== +valid valid float_exprs.wast:2542 AGFzbQEAAAABBgFgAX8BfAMCAQAHBwEDdGF1AAAKywEByAEBBHxEAAAAAAAAAAAhAQJAIABBAUgNAEQAAAAAAADwPyECRAAAAAAAAAAAIQMDQCABIAJEAAAAAAAAIEAgA0QAAAAAAAAgQKIiBEQAAAAAAADwP6CjRAAAAAAAABBAIAREAAAAAAAAEECgo6FEAAAAAAAAAEAgBEQAAAAAAAAUQKCjoUQAAAAAAAAAQCAERAAAAAAAABhAoKOhoqAhASADRAAAAAAAAPA/oCEDIAJEAAAAAAAAsD+iIQIgAEF/aiIADQALCyABCw== +valid valid float_exprs.wast:2612 AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc9AhtmMzIubm9fZm9sZF9jb25kaXRpb25hbF9pbmMAABtmNjQubm9fZm9sZF9jb25kaXRpb25hbF9pbmMAAQo1AhUAIAAgAEMAAIA/kiABQwAAAABdGwsdACAAIABEAAAAAAAA8D+gIAFEAAAAAAAAAABjGws= +valid valid float_literals.wast:3 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== +valid valid float_literals.wast:224 AGFzbQEAAAABhYCAgAABYAABfAOCgICAAAEAB46AgIAAAQo0Mjk0OTY3MjQ5AAAKkYCAgAABi4CAgAAARAAAIPr//+9BCw== +valid valid float_memory.wast:5 AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBACoCAAsHAEEAKAIACwwAQQBDAACgfzgCAAsNAEEAQYCAgP0HNgIACwkAQQBBADYCAAsLCgEAQQALBAAAoH8= +valid valid float_memory.wast:30 AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBACsDAAsHAEEAKQMACxAAQQBEAAAAAAAA9H85AwALEgBBAEKAgICAgICA+v8ANwMACwkAQQBCADcDAAsLDgEAQQALCAAAAAAAAPR/ +valid valid float_memory.wast:57 AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBASoCAAsHAEEBKAIACwwAQQFDAACgfzgCAAsNAEEBQYCAgP0HNgIACwkAQQFBADYCAAsLCwEAQQALBQAAAKB/ +valid valid float_memory.wast:82 AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBASsDAAsHAEEBKQMACxAAQQFEAAAAAAAA9H85AwALEgBBAUKAgICAgICA+v8ANwMACwkAQQFCADcDAAsLDwEAQQALCQAAAAAAAAD0fw== +valid valid float_memory.wast:109 AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBACoCAAsHAEEAKAIACwwAQQBDAQDQfzgCAAsNAEEAQYGAwP4HNgIACwkAQQBBADYCAAsLCgEAQQALBAEA0H8= +valid valid float_memory.wast:134 AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBACsDAAsHAEEAKQMACxAAQQBEAQAAAAAA/H85AwALEgBBAEKBgICAgICA/v8ANwMACwkAQQBCADcDAAsLDgEAQQALCAEAAAAAAPx/ +valid valid float_misc.wast:17 AGFzbQEAAAABFwRgAn19AX1gAX0BfWACfHwBfGABfAF8Ax0cAAAAAAEBAQABAQEBAAACAgICAwMDAgMDAwMCAge3AhwHZjMyLmFkZAAAB2YzMi5zdWIAAQdmMzIubXVsAAIHZjMyLmRpdgADCGYzMi5zcXJ0AAQHZjMyLmFicwAFB2YzMi5uZWcABgxmMzIuY29weXNpZ24ABwhmMzIuY2VpbAAICWYzMi5mbG9vcgAJCWYzMi50cnVuYwAKC2YzMi5uZWFyZXN0AAsHZjMyLm1pbgAMB2YzMi5tYXgADQdmNjQuYWRkAA4HZjY0LnN1YgAPB2Y2NC5tdWwAEAdmNjQuZGl2ABEIZjY0LnNxcnQAEgdmNjQuYWJzABMHZjY0Lm5lZwAUDGY2NC5jb3B5c2lnbgAVCGY2NC5jZWlsABYJZjY0LmZsb29yABcJZjY0LnRydW5jABgLZjY0Lm5lYXJlc3QAGQdmNjQubWluABoHZjY0Lm1heAAbCsUBHAcAIAAgAZILBwAgACABkwsHACAAIAGUCwcAIAAgAZULBQAgAJELBQAgAIsLBQAgAIwLBwAgACABmAsFACAAjQsFACAAjgsFACAAjwsFACAAkAsHACAAIAGWCwcAIAAgAZcLBwAgACABoAsHACAAIAGhCwcAIAAgAaILBwAgACABowsFACAAnwsFACAAmQsFACAAmgsHACAAIAGmCwUAIACbCwUAIACcCwUAIACdCwUAIACeCwcAIAAgAaQLBwAgACABpQs= +valid valid forward.wast:1 AGFzbQEAAAABBgFgAX8BfwMDAgAABw4CBGV2ZW4AAANvZGQAAQorAhQAIABBAEYEf0EBBSAAQQFrEAELCxQAIABBAEYEf0EABSAAQQFrEAALCw== +valid valid func.wast:3 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= +valid valid func.wast:422 AGFzbQEAAAABDANgAX8AYAABfGAAAAMGBQEAAAECCikFCwBEAAAAAAAAAAALAgALAgALCwBEAAAAAAAAAAALCQBBABACEAMaCw== +invalid invalid func.wast:436 AGFzbQEAAAABCQJgAX8AYAABfAMFBAEAAQIKHwQLAEQAAAAAAAAAAAsCAAsLAEQAAAAAAADwPwsCAAs= +valid valid func.wast:459 AGFzbQEAAAABCgJgAAF/YAF/AX8DBQQBAQEABw0DAWYAAAFnAAIBcAADCiEEBgEBfyABCwYBAX8gAQsGACAAEAELCgEBf0EqIQAgAAs= +valid valid func.wast:488 AGFzbQEAAAABHQRgAABgAABgCH5+fH58fn1/AGAIfH58fnx+fX8AAwwLAAMAAwMCAgAAAAAEBQFwAQcHB3cEGXNpZ25hdHVyZS1leHBsaWNpdC1yZXVzZWQABxlzaWduYXR1cmUtaW1wbGljaXQtcmV1c2VkAAgcc2lnbmF0dXJlLWV4cGxpY2l0LWR1cGxpY2F0ZQAJHHNpZ25hdHVyZS1pbXBsaWNpdC1kdXBsaWNhdGUACgkNAQBBAAsHBAIBBAAFBgqFAgsCAAsCAAsCAAsCAAsCAAsCAAsCAAsMAEEBEQAAQQQRAAALiQEARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEAEQMARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEECEQMARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEDEQMACwcAQQERAQALTgBCAEIARAAAAAAAAAAAQgBEAAAAAAAAAABCAEMAAAAAQQBBBRECAEIAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEGEQIACw== +invalid invalid func.wast:634 AGFzbQEAAAABBQFgAAF+AwIBAAoIAQYBAX8gAAs= +invalid invalid func.wast:638 AGFzbQEAAAABBAFgAAADAgEACgkBBwEBfSAARQs= +invalid invalid func.wast:642 AGFzbQEAAAABBAFgAAADAgEACgsBCQIBfAF+IAGaCw== +invalid invalid func.wast:650 AGFzbQEAAAABBgFgAX8BfgMCAQAKBgEEACAACw== +invalid invalid func.wast:654 AGFzbQEAAAABBQFgAX0AAwIBAAoHAQUAIABFCw== +invalid invalid func.wast:658 AGFzbQEAAAABBgFgAnx+AAMCAQAKBwEFACABmgs= +invalid invalid func.wast:666 AGFzbQEAAAABBQFgAAF/AwIBAAoEAQIACw== +invalid invalid func.wast:670 AGFzbQEAAAABBQFgAAF+AwIBAAoEAQIACw== +invalid invalid func.wast:674 AGFzbQEAAAABBQFgAAF9AwIBAAoEAQIACw== +invalid invalid func.wast:678 AGFzbQEAAAABBQFgAAF8AwIBAAoEAQIACw== +invalid invalid func.wast:682 AGFzbQEAAAABBgFgAAJ8fwMCAQAKBAECAAs= +invalid invalid func.wast:687 AGFzbQEAAAABBQFgAAF/AwIBAAoFAQMAAQs= +invalid invalid func.wast:693 AGFzbQEAAAABBgFgAAJ/fwMCAQAKBQEDAAEL +invalid invalid func.wast:699 AGFzbQEAAAABBAFgAAADAgEACgYBBABBAAs= +invalid invalid func.wast:705 AGFzbQEAAAABBAFgAAADAgEACggBBgBBAEIACw== +invalid invalid func.wast:711 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcAQwAAAAAL +invalid invalid func.wast:717 AGFzbQEAAAABBgFgAAJ9fQMCAQAKCQEHAEMAAAAACw== +invalid invalid func.wast:723 AGFzbQEAAAABBQFgAAF9AwIBAAoOAQwAQwAAAABDAAAAAAs= +invalid invalid func.wast:730 AGFzbQEAAAABBQFgAAF/AwIBAAoFAQMADws= +invalid invalid func.wast:736 AGFzbQEAAAABBgFgAAJ/fwMCAQAKBQEDAA8L +invalid invalid func.wast:742 AGFzbQEAAAABBQFgAAF/AwIBAAoGAQQAAQ8L +invalid invalid func.wast:748 AGFzbQEAAAABBgFgAAJ/fgMCAQAKBgEEAAEPCw== +invalid invalid func.wast:754 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgAPCw== +invalid invalid func.wast:760 AGFzbQEAAAABBgFgAAJ+fgMCAQAKBwEFAEIADws= +invalid invalid func.wast:767 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAD0EBCw== +invalid invalid func.wast:773 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCQEHAA9BAUECCw== +invalid invalid func.wast:779 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCQEHAEEBD0ECCw== +invalid invalid func.wast:785 AGFzbQEAAAABBQFgAAF/AwIBAAoIAQYAAQ9BAQs= +invalid invalid func.wast:791 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCAEGAAEPQQEL +invalid invalid func.wast:797 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcAQgEPQQEL +invalid invalid func.wast:803 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCwEJAEIBD0EBQQIL +invalid invalid func.wast:809 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAQgEPQQEPCw== +invalid invalid func.wast:815 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDAEKAEEBD0EBQQIPCw== +invalid invalid func.wast:822 AGFzbQEAAAABBQFgAAF/AwIBAAoGAQQADAAL +invalid invalid func.wast:828 AGFzbQEAAAABBgFgAAJ/fwMCAQAKBgEEAAwACw== +invalid invalid func.wast:834 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAQwAAAAAMAAs= +invalid invalid func.wast:840 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCAEGAEEADAAL +invalid invalid func.wast:846 AGFzbQEAAAABBQFgAAF/AwIBAAoIAQYADABBAQs= +invalid invalid func.wast:852 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCgEIAAwAQQFBAgs= +invalid invalid func.wast:858 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAQgEMAEEBCw== +invalid invalid func.wast:864 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDAEKAEEBDABBAUECCw== +invalid invalid func.wast:870 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgEMAEEBDAAL +invalid invalid func.wast:877 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAAkAMAQtBAQwACw== +invalid invalid func.wast:883 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDwENAAJADAELQQFBAgwACw== +invalid invalid func.wast:889 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAkABDAELQQEMAAs= +invalid invalid func.wast:895 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEAEOAAJAAQwBC0EBQQIMAAs= +invalid invalid func.wast:901 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAkBCAQwBC0EBDAAL +invalid invalid func.wast:907 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEQEPAAJ/QQEMAQtBAUECDAAL +valid valid func_ptrs.wast:1 AGFzbQEAAAABGwdgAABgAABgAABgAAF/YAABf2ABfwF/YAF/AAIWAQhzcGVjdGVzdAlwcmludF9pMzIABgMHBgABBAUFBgccBANvbmUAAwN0d28ABAV0aHJlZQAFBGZvdXIABgojBgIACwIACwQAQQ0LBwAgAEEBagsHACAAQQJrCwYAIAAQAAs= +invalid invalid func_ptrs.wast:32 AGFzbQEAAAAJBgEAQQALAA== +invalid invalid func_ptrs.wast:48 AGFzbQEAAAADAgEqCgQBAgAL +invalid invalid func_ptrs.wast:49 AGFzbQEAAAACFgEIc3BlY3Rlc3QJcHJpbnRfaTMyACs= +valid valid func_ptrs.wast:51 AGFzbQEAAAABDgNgAAF/YAABf2ABfwF/AwgHAAAAAQECAgQFAXABBwcHEQIFY2FsbHQABQVjYWxsdQAGCQ0BAEEACwcAAQIDBAACCioHBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwcAIAARAAALBwAgABEBAAs= +valid valid func_ptrs.wast:93 AGFzbQEAAAABCgJgAAF/YAF/AX8DBAMAAAEEBQFwAQICBwkBBWNhbGx0AAIJCAEAQQALAgABChMDBABBAQsEAEECCwcAIAARAAAL +valid valid global.wast:3 AGFzbQEAAAABNw1gAn9/AX9gAAF/YAABfmAAAW9gAX8AYAF+AGABbwBgAAF9YAABfGABfQBgAXwAYAAAYAF/AX8CLwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdApnbG9iYWxfaTY0A34AAzMyAQIDAwECAQIEBQYHCAcICQoLAQEBAQEBAQEBAQEBAQABAQELCwEBDAEBCwEMDAEBAQEEBQFwAQEBBQMBAAEGVg1/AEF+C30AQwAAQMALfABEAAAAAAAAEMALfgBCewt/AUF0C30BQwAAUMELfAFEAAAAAAAALMALfgFCcQt/ACMAC34AIwELbwDQbwtvAdBvC3AA0HALB7EFLwVnZXQtYQAABWdldC1iAAEFZ2V0LXIAAgZnZXQtbXIAAwVnZXQteAAEBWdldC15AAUGZ2V0LXoxAAYGZ2V0LXoyAAcFc2V0LXgACAVzZXQteQAJBnNldC1tcgAKBWdldC0zAAsFZ2V0LTQADAVnZXQtNwANBWdldC04AA4Fc2V0LTcADwVzZXQtOAAQD2FzLXNlbGVjdC1maXJzdAASDWFzLXNlbGVjdC1taWQAEw5hcy1zZWxlY3QtbGFzdAAUDWFzLWxvb3AtZmlyc3QAFQthcy1sb29wLW1pZAAWDGFzLWxvb3AtbGFzdAAXD2FzLWlmLWNvbmRpdGlvbgAYCmFzLWlmLXRoZW4AGQphcy1pZi1lbHNlABoOYXMtYnJfaWYtZmlyc3QAGw1hcy1icl9pZi1sYXN0ABwRYXMtYnJfdGFibGUtZmlyc3QAHRBhcy1icl90YWJsZS1sYXN0AB4WYXMtY2FsbF9pbmRpcmVjdC1maXJzdAAgFGFzLWNhbGxfaW5kaXJlY3QtbWlkACEVYXMtY2FsbF9pbmRpcmVjdC1sYXN0ACIOYXMtc3RvcmUtZmlyc3QAIw1hcy1zdG9yZS1sYXN0ACQPYXMtbG9hZC1vcGVyYW5kACUUYXMtbWVtb3J5Lmdyb3ctdmFsdWUAJg1hcy1jYWxsLXZhbHVlACgPYXMtcmV0dXJuLXZhbHVlACkPYXMtZHJvcC1vcGVyYW5kACoLYXMtYnItdmFsdWUAKxJhcy1sb2NhbC5zZXQtdmFsdWUALBJhcy1sb2NhbC50ZWUtdmFsdWUALRNhcy1nbG9iYWwuc2V0LXZhbHVlAC4QYXMtdW5hcnktb3BlcmFuZAAvEWFzLWJpbmFyeS1vcGVyYW5kADASYXMtY29tcGFyZS1vcGVyYW5kADEJBwEAQQALAR8KsAMyBAAjAgsEACMFCwQAIwwLBAAjDQsEACMGCwQAIwkLBAAjCgsEACMLCwYAIAAkBgsGACAAJAkLBgAgACQNCwQAIwMLBAAjBAsEACMHCwQAIwgLBgAgACQHCwYAIAAkCAsCAAsJACMGQQJBAxsLCQBBAiMGQQMbCwkAQQJBAyMGGwsLAAN/IwYQERARCwsLAAN/EBEjBhARCwsLAAN/EBEQESMGCwsQACMGBH8QEUECBRARQQMLCwwAQQEEfyMGBUECCwsMAEEABH9BAgUjBgsLDgACfyMGQQINAEEDDwsLDgACf0ECIwYNAEEDDwsLDQACfyMGQQIOAQAACwsNAAJ/QQIjBg4BAAALCwQAIAALDgACfyMGQQJBABEAAAsLDgACf0ECIwZBABEAAAsLDgACf0ECQQAjBhEAAAsLCQAjBkEBNgIACwkAQQAjBjYCAAsHACMGKAIACwYAIwZAAAsEACAACwYAIwYQJwsFACMGDwsFACMGGgsJAAJ/IwYMAAsLCAAjBiEAIAALBgAjBiIACwgAIwYkBiMGCwUAIwZFCwcAIwYjBmwLBwAjAEEBSws= +invalid invalid global.wast:273 AGFzbQEAAAABBAFgAAADAgEABgkBfQBDAAAAAAsKCwEJAEMAAIA/JAAL +invalid invalid global.wast:278 AGFzbQEAAAABBAFgAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMCAQAKCAEGAEEBJAAL +valid valid global.wast:283 AGFzbQEAAAAGCQF9AUMAAAAACwcFAQFhAwA= +invalid invalid global.wast:287 AGFzbQEAAAAGCgF9AEMAAAAAjAs= +invalid invalid global.wast:292 AGFzbQEAAAAGBgF9ACAACw== +invalid invalid global.wast:297 AGFzbQEAAAAGCgF9AEMAAIA/jAs= +invalid invalid global.wast:302 AGFzbQEAAAAGBwF/AEEAAQs= +invalid invalid global.wast:307 AGFzbQEAAAAGBwF/AEEAaAs= +invalid invalid global.wast:312 AGFzbQEAAAAGBQF/AAEL +invalid invalid global.wast:317 AGFzbQEAAAAGCQF/AEMAAAAACw== +invalid invalid global.wast:322 AGFzbQEAAAAGCAF/AEEAQQAL +invalid invalid global.wast:327 AGFzbQEAAAAGBAF/AAs= +invalid invalid global.wast:332 AGFzbQEAAAACBgEAAANvAAYGAXAAIwAL +invalid invalid global.wast:337 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABggBfwAjACMACw== +invalid invalid global.wast:342 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABggBfwBBACMACw== +invalid invalid global.wast:347 AGFzbQEAAAAGBgF/ACMACw== +invalid valid global.wast:352 AGFzbQEAAAAGCwJ/AEEAC38AIwAL +invalid invalid global.wast:361 AGFzbQEAAAAGCwJ/ACMBC38AQQAL +invalid invalid global.wast:366 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38ABgYBfwAjAgs= +invalid invalid global.wast:371 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWkzMgN/AQYGAX8AIwAL +valid valid global.wast:375 AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AA== +malformed invalid global.wast:379 AGFzbQEAAAACmICAgAABCHNwZWN0ZXN0Cmdsb2JhbF9pMzIDfwQ= +malformed invalid global.wast:392 AGFzbQEAAAACmICAgAABCHNwZWN0ZXN0Cmdsb2JhbF9pMzIDf/8= +valid valid global.wast:405 AGFzbQEAAAAGBgF/AEEACw== +malformed invalid global.wast:409 AGFzbQEAAAAGhoCAgAABfwRBAAs= +malformed invalid global.wast:421 AGFzbQEAAAAGhoCAgAABf/9BAAs= +invalid invalid global.wast:435 AGFzbQEAAAABBQFgAAF/AwIBAAoGAQQAIwAL +invalid invalid global.wast:440 AGFzbQEAAAABBQFgAAF/AwIBAAYGAX8AQQALCgYBBAAjAQs= +invalid invalid global.wast:448 AGFzbQEAAAABBQFgAAF/AhgBCHNwZWN0ZXN0Cmdsb2JhbF9pMzIDfwADAgEACgYBBAAjAQs= +invalid invalid global.wast:456 AGFzbQEAAAABBQFgAAF/AhgBCHNwZWN0ZXN0Cmdsb2JhbF9pMzIDfwADAgEABgYBfwBBAAsKBgEEACMCCw== +invalid invalid global.wast:466 AGFzbQEAAAABBAFgAAADAgEACggBBgBBACQACw== +invalid invalid global.wast:471 AGFzbQEAAAABBAFgAAADAgEABgYBfwBBAAsKCAEGAEEAJAEL +invalid invalid global.wast:479 AGFzbQEAAAABBAFgAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMCAQAKCAEGAEEAJAEL +invalid invalid global.wast:487 AGFzbQEAAAABBAFgAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMCAQAGBgF/AEEACwoIAQYAQQAkAgs= +invalid invalid global.wast:497 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKBgEEACQACw== +invalid invalid global.wast:506 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKCwEJAEEAAkAkAAsL +invalid invalid global.wast:516 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKCwEJAEEAA0AkAAsL +invalid invalid global.wast:526 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKDQELAEEAQQAEQCQACws= +invalid invalid global.wast:536 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKEAEOAEEAQQAEf0EABSQACws= +invalid invalid global.wast:546 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKDQELAEEAAkAkAAwACws= +invalid invalid global.wast:556 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKDQELAEEAAkAkAA0ACws= +invalid invalid global.wast:566 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKDgEMAEEAAkAkAA4AAAsL +invalid invalid global.wast:576 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKBwEFACQADws= +invalid invalid global.wast:585 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKCwEJACQAQQFBAhsL +invalid invalid global.wast:594 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBgYBfwFBAAsKDQIGACQAEAELBAAgAAs= +invalid invalid global.wast:604 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQYGAX8BQQALCQcBAEEACwEAChMCBAAgAAsMAAJ/JABBABEAAAsL +valid valid i32.wast:3 AGFzbQEAAAABDAJgAn9/AX9gAX8BfwMgHwAAAAAAAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAAAAAAH3gEfA2FkZAAAA3N1YgABA211bAACBWRpdl9zAAMFZGl2X3UABAVyZW1fcwAFBXJlbV91AAYDYW5kAAcCb3IACAN4b3IACQNzaGwACgVzaHJfcwALBXNocl91AAwEcm90bAANBHJvdHIADgNjbHoADwNjdHoAEAZwb3BjbnQAEQlleHRlbmQ4X3MAEgpleHRlbmQxNl9zABMDZXF6ABQCZXEAFQJuZQAWBGx0X3MAFwRsdF91ABgEbGVfcwAZBGxlX3UAGgRndF9zABsEZ3RfdQAcBGdlX3MAHQRnZV91AB4K7QEfBwAgACABagsHACAAIAFrCwcAIAAgAWwLBwAgACABbQsHACAAIAFuCwcAIAAgAW8LBwAgACABcAsHACAAIAFxCwcAIAAgAXILBwAgACABcwsHACAAIAF0CwcAIAAgAXULBwAgACABdgsHACAAIAF3CwcAIAAgAXgLBQAgAGcLBQAgAGgLBQAgAGkLBQAgAMALBQAgAMELBQAgAEULBwAgACABRgsHACAAIAFHCwcAIAAgAUgLBwAgACABSQsHACAAIAFMCwcAIAAgAU0LBwAgACABSgsHACAAIAFLCwcAIAAgAU4LBwAgACABTws= +invalid invalid i32.wast:444 AGFzbQEAAAABBAFgAAADAgEACgYBBABFGgs= +invalid invalid i32.wast:452 AGFzbQEAAAABBAFgAAADAgEACgsBCQBBAAJARRoLCw== +invalid invalid i32.wast:461 AGFzbQEAAAABBAFgAAADAgEACgsBCQBBAANARRoLCw== +invalid invalid i32.wast:470 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAEEABEBFGgsL +invalid invalid i32.wast:479 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAEEABH9BAAVFCxoL +invalid invalid i32.wast:488 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAAJARQwAGgsL +invalid invalid i32.wast:497 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAAJARUEBDQAaCws= +invalid invalid i32.wast:506 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAAJARQ4AABoLCw== +invalid invalid i32.wast:515 AGFzbQEAAAABBAFgAAADAgEACgcBBQBFDxoL +invalid invalid i32.wast:523 AGFzbQEAAAABBAFgAAADAgEACgsBCQBFQQFBAhsaCw== +invalid invalid i32.wast:531 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABCg0CBgBFEAEaCwQAIAAL +invalid invalid i32.wast:540 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoTAgQAIAALDAACf0VBABEAABoLCw== +invalid invalid i32.wast:556 AGFzbQEAAAABBAFgAAADAgEACgwBCgEBf0UhACAAGgs= +invalid invalid i32.wast:565 AGFzbQEAAAABBAFgAAADAgEACgoBCAEBf0UiABoL +invalid invalid i32.wast:574 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKCgEIAEUkACMAGgs= +invalid invalid i32.wast:583 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCAEGAEVAABoL +invalid invalid i32.wast:592 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCQEHAEUoAgAaCw== +invalid invalid i32.wast:601 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEVBATYCAAs= +invalid invalid i32.wast:611 AGFzbQEAAAABBAFgAAADAgEACgYBBABqGgs= +invalid invalid i32.wast:619 AGFzbQEAAAABBAFgAAADAgEACggBBgBBAGoaCw== +invalid invalid i32.wast:627 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAEEAAkBqGgsL +invalid invalid i32.wast:636 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAAJAQQBqGgsL +invalid invalid i32.wast:645 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAEEAA0BqGgsL +invalid invalid i32.wast:654 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAANAQQBqGgsL +invalid invalid i32.wast:663 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEAQQBqBEAaCws= +invalid invalid i32.wast:672 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAEEAQQAEQGoFGgsL +invalid invalid i32.wast:681 AGFzbQEAAAABBAFgAAADAgEAChUBEwBBAEEAQQAEf0EABWpBAAsaGgs= +invalid invalid i32.wast:691 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAEEABH9BAAVqCxoL +invalid invalid i32.wast:701 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEAAkBqDAAaCws= +invalid invalid i32.wast:710 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAAJAQQBqDAAaCws= +invalid invalid i32.wast:719 AGFzbQEAAAABBAFgAAADAgEAChEBDwBBAEEAAkBqQQENABoLCw== +invalid invalid i32.wast:728 AGFzbQEAAAABBAFgAAADAgEAChEBDwBBAAJAQQBqQQENABoLCw== +invalid invalid i32.wast:737 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAEEAAkBqDgAAGgsL +invalid invalid i32.wast:746 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAAJAQQBqDgAAGgsL +invalid invalid i32.wast:755 AGFzbQEAAAABBAFgAAADAgEACgcBBQBqDxoL +invalid invalid i32.wast:763 AGFzbQEAAAABBAFgAAADAgEACgkBBwBBAGoPGgs= +invalid invalid i32.wast:771 AGFzbQEAAAABBAFgAAADAgEACgsBCQBqQQFBAhsaCw== +invalid invalid i32.wast:779 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAGpBAUECGxoL +invalid invalid i32.wast:787 AGFzbQEAAAABCgJgAABgAn9/AX8DAwIAAQoNAgYAahABGgsEACAACw== +invalid invalid i32.wast:796 AGFzbQEAAAABCgJgAABgAn9/AX8DAwIAAQoPAggAQQBqEAEaCwQAIAAL +invalid invalid i32.wast:805 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoTAgQAIAALDAACf2pBABEAABoLCw== +invalid invalid i32.wast:821 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoVAgQAIAALDgACf0EAakEAEQAAGgsL +invalid invalid i32.wast:837 AGFzbQEAAAABBAFgAAADAgEACgwBCgEBf2ohACAAGgs= +invalid invalid i32.wast:846 AGFzbQEAAAABBAFgAAADAgEACg4BDAEBf0EAaiEAIAAaCw== +invalid invalid i32.wast:855 AGFzbQEAAAABBAFgAAADAgEACgoBCAEBf2oiABoL +invalid invalid i32.wast:864 AGFzbQEAAAABBAFgAAADAgEACgwBCgEBf0EAaiIAGgs= +invalid invalid i32.wast:873 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKCgEIAGokACMAGgs= +invalid invalid i32.wast:882 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKDAEKAEEAaiQAIwAaCw== +invalid invalid i32.wast:891 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCAEGAGpAABoL +invalid invalid i32.wast:900 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAakAAGgs= +invalid invalid i32.wast:909 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCQEHAGooAgAaCw== +invalid invalid i32.wast:918 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAaigCABoL +invalid invalid i32.wast:927 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAGpBATYCAAs= +invalid invalid i32.wast:936 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDAEKAEEBakEANgIACw== +invalid invalid i32.wast:948 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAGoL +invalid invalid i32.wast:949 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHEL +invalid invalid i32.wast:950 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAG0L +invalid invalid i32.wast:951 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAG4L +invalid invalid i32.wast:952 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAGwL +invalid invalid i32.wast:953 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHIL +invalid invalid i32.wast:954 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAG8L +invalid invalid i32.wast:955 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHAL +invalid invalid i32.wast:956 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHcL +invalid invalid i32.wast:957 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHgL +invalid invalid i32.wast:958 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHQL +invalid invalid i32.wast:959 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHUL +invalid invalid i32.wast:960 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHYL +invalid invalid i32.wast:961 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAGsL +invalid invalid i32.wast:962 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAHML +invalid invalid i32.wast:963 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgBFCw== +invalid invalid i32.wast:964 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgBnCw== +invalid invalid i32.wast:965 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgBoCw== +invalid invalid i32.wast:966 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAQgBpCw== +invalid invalid i32.wast:967 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAEYL +invalid invalid i32.wast:968 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAE4L +invalid invalid i32.wast:969 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAE8L +invalid invalid i32.wast:970 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAEoL +invalid invalid i32.wast:971 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAEsL +invalid invalid i32.wast:972 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAEwL +invalid invalid i32.wast:973 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAE0L +invalid invalid i32.wast:974 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAEgL +invalid invalid i32.wast:975 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAEkL +invalid invalid i32.wast:976 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQgBDAAAAAEcL +valid valid i64.wast:3 AGFzbQEAAAABFwRgAn5+AX5gAX4BfmABfgF/YAJ+fgF/AyEgAAAAAAAAAAAAAAAAAAAAAQEBAQEBAgMDAwMDAwMDAwMH6wEgA2FkZAAAA3N1YgABA211bAACBWRpdl9zAAMFZGl2X3UABAVyZW1fcwAFBXJlbV91AAYDYW5kAAcCb3IACAN4b3IACQNzaGwACgVzaHJfcwALBXNocl91AAwEcm90bAANBHJvdHIADgNjbHoADwNjdHoAEAZwb3BjbnQAEQlleHRlbmQ4X3MAEgpleHRlbmQxNl9zABMKZXh0ZW5kMzJfcwAUA2VxegAVAmVxABYCbmUAFwRsdF9zABgEbHRfdQAZBGxlX3MAGgRsZV91ABsEZ3RfcwAcBGd0X3UAHQRnZV9zAB4EZ2VfdQAfCvMBIAcAIAAgAXwLBwAgACABfQsHACAAIAF+CwcAIAAgAX8LBwAgACABgAsHACAAIAGBCwcAIAAgAYILBwAgACABgwsHACAAIAGECwcAIAAgAYULBwAgACABhgsHACAAIAGHCwcAIAAgAYgLBwAgACABiQsHACAAIAGKCwUAIAB5CwUAIAB6CwUAIAB7CwUAIADCCwUAIADDCwUAIADECwUAIABQCwcAIAAgAVELBwAgACABUgsHACAAIAFTCwcAIAAgAVQLBwAgACABVwsHACAAIAFYCwcAIAAgAVULBwAgACABVgsHACAAIAFZCwcAIAAgAVoL +invalid invalid i64.wast:457 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAHwL +invalid invalid i64.wast:458 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIML +invalid invalid i64.wast:459 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAH8L +invalid invalid i64.wast:460 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIAL +invalid invalid i64.wast:461 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAH4L +invalid invalid i64.wast:462 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIQL +invalid invalid i64.wast:463 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIEL +invalid invalid i64.wast:464 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIIL +invalid invalid i64.wast:465 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIkL +invalid invalid i64.wast:466 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIoL +invalid invalid i64.wast:467 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIYL +invalid invalid i64.wast:468 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIcL +invalid invalid i64.wast:469 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIgL +invalid invalid i64.wast:470 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAH0L +invalid invalid i64.wast:471 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAIUL +invalid invalid i64.wast:472 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQBQCw== +invalid invalid i64.wast:473 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQB5Cw== +invalid invalid i64.wast:474 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQB6Cw== +invalid invalid i64.wast:475 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAQQB7Cw== +invalid invalid i64.wast:476 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFEL +invalid invalid i64.wast:477 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFkL +invalid invalid i64.wast:478 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFoL +invalid invalid i64.wast:479 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFUL +invalid invalid i64.wast:480 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFYL +invalid invalid i64.wast:481 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFcL +invalid invalid i64.wast:482 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFgL +invalid invalid i64.wast:483 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFML +invalid invalid i64.wast:484 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFQL +invalid invalid i64.wast:485 AGFzbQEAAAABBQFgAAF+AwIBAAoMAQoAQQBDAAAAAFIL +valid valid if.wast:3 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== +invalid invalid if.wast:836 AGFzbQEAAAABBAFgAAADAgEACgsBCQBBAUEABEALCw== +invalid invalid if.wast:844 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcAQQAEQAsL +invalid invalid if.wast:848 AGFzbQEAAAABBQFgAAF+AwIBAAoJAQcAQQAEQAsL +invalid invalid if.wast:852 AGFzbQEAAAABBQFgAAF9AwIBAAoJAQcAQQAEQAsL +invalid invalid if.wast:856 AGFzbQEAAAABBQFgAAF8AwIBAAoJAQcAQQAEQAsL +invalid invalid if.wast:878 AGFzbQEAAAABBAFgAAADAgEACgsBCQBBAQRAQQELCw== +invalid invalid if.wast:890 AGFzbQEAAAABBAFgAAADAgEACgwBCgBBAQRABUEBCws= +invalid invalid if.wast:896 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAQRAQQEFQQELCw== +invalid invalid if.wast:903 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAQRAQQFBAgsL +invalid invalid if.wast:915 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAQRABUEBQQILCw== +invalid invalid if.wast:921 AGFzbQEAAAABBAFgAAADAgEAChIBEABBAQRAQQFBAgVBAkEBCws= +invalid invalid if.wast:928 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQQEEfwVBAAsL +invalid invalid if.wast:934 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAQQEEf0EACws= +invalid invalid if.wast:940 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcAQQEEfwsL +invalid invalid if.wast:947 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDgEMAEEBBAAFQQBBAgsL +invalid invalid if.wast:953 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDQELAEEBBABBAEEBCws= +invalid invalid if.wast:959 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCQEHAEEBBAALCw== +invalid invalid if.wast:966 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAQQEEf0EBCws= +invalid invalid if.wast:972 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDQELAEEBBABBAUEBCws= +invalid invalid if.wast:979 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAQQEEfwEFQQALCw== +invalid invalid if.wast:985 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAQQEEf0EABQELCw== +invalid invalid if.wast:991 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQQEEfwEFAQsL +invalid invalid if.wast:998 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDwENAEEBBAABBUEAQQALCw== +invalid invalid if.wast:1004 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDwENAEEBBABBAEEABQELCw== +invalid invalid if.wast:1010 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDAEKAEEBBAABBQELCw== +invalid invalid if.wast:1017 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAQQEEf0IBBUEBCws= +invalid invalid if.wast:1023 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAQQEEf0EBBUIBCws= +invalid invalid if.wast:1029 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAQQEEf0IBBUIBCws= +invalid invalid if.wast:1036 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEAEOAEEBBABBAQVBAUEBCws= +invalid invalid if.wast:1042 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEAEOAEEBBABBAUEBBUEBCws= +invalid invalid if.wast:1048 AGFzbQEAAAABBgFgAAJ/fwMCAQAKDgEMAEEBBABBAQVBAQsL +invalid invalid if.wast:1055 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEgEQAEEAQQEEAEEBBUEBQQELCw== +invalid invalid if.wast:1062 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEgEQAEEAQQEEAEEBQQEFQQELCw== +invalid invalid if.wast:1069 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEAEOAEEAQQEEAEEBBUEBCws= +invalid invalid if.wast:1077 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AQQEEf0EBQQEFQQELCw== +invalid invalid if.wast:1083 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AQQEEf0EBBUEBQQELCw== +invalid invalid if.wast:1089 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAQQEEf0EBQQEFQQFBAQsL +invalid invalid if.wast:1096 AGFzbQEAAAABBQFgAAF/AwIBAAoVARMAQQEEf0IBBUQAAAAAAADwPwsL +invalid invalid if.wast:1102 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEgEQAEEBBABBAUEBQQEFQQELCw== +invalid invalid if.wast:1109 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AQQAEfgAAABsFQgALCw== +invalid invalid if.wast:1119 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AQQEEfkIABQAAABsLCw== +invalid invalid if.wast:1129 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAQQEEfgAAABsFAAAAGwsL +invalid invalid if.wast:1140 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAQQEEfwwABUEBCws= +invalid invalid if.wast:1146 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAQQEEf0EBBQwACws= +invalid invalid if.wast:1152 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEAEOAEEBBAAMAAVBAUEBCws= +invalid invalid if.wast:1158 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEAEOAEEBBABBAUEBBQwACws= +invalid invalid if.wast:1165 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AQQEEfwwAQQEFQQELCw== +invalid invalid if.wast:1174 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AQQEEf0EBBQwAQQELCw== +invalid invalid if.wast:1183 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFAESAEEBBAAMAEEBQQEFQQFBAQsL +invalid invalid if.wast:1192 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFAESAEEBBABBAUEBBQwAQQFBAQsL +invalid invalid if.wast:1202 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AQQEEfwEMAEEBBUEBCws= +invalid invalid if.wast:1211 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AQQEEf0EBBQEMAEEBCws= +invalid invalid if.wast:1220 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFQETAEEBBAABDABBAUEBBUEBQQELCw== +invalid invalid if.wast:1229 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFQETAEEBBABBAUEBBQEMAEEBQQELCw== +invalid invalid if.wast:1239 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAQQEEf0IBDABBAQVBAQsL +invalid invalid if.wast:1248 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAQQEEf0EBBUIBDABBAQsL +invalid invalid if.wast:1257 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFgEUAEEBBABCAQwAQQFBAQVBAUEBCws= +invalid invalid if.wast:1266 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFgEUAEEBBABBAUEBBUIBDABBAUEBCws= +invalid invalid if.wast:1275 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFAESAEEBQQEEAEIBDABBAQVBAQsL +invalid invalid if.wast:1285 AGFzbQEAAAABBgFgAAJ/fwMCAQAKFAESAEEBQQEEAEEBBUIBDABBAQsL +invalid invalid if.wast:1296 AGFzbQEAAAABBAFgAAADAgEACgcBBQAEQAsL +invalid invalid if.wast:1304 AGFzbQEAAAABBAFgAAADAgEACgwBCgBBAAJABEALCws= +invalid invalid if.wast:1313 AGFzbQEAAAABBAFgAAADAgEACgwBCgBBAANABEALCws= +invalid invalid if.wast:1322 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAEEABEAEQAsLCw== +invalid invalid if.wast:1331 AGFzbQEAAAABBAFgAAADAgEAChQBEgBBAEEABH9BAAUEQAtBAAsaCw== +invalid invalid if.wast:1341 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAAJABEALDAAaCws= +invalid invalid if.wast:1350 AGFzbQEAAAABBAFgAAADAgEAChEBDwBBAAJABEALQQENABoLCw== +invalid invalid if.wast:1359 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAAJABEALDgAAGgsL +invalid invalid if.wast:1368 AGFzbQEAAAABBAFgAAADAgEACgkBBwAEQAsPGgs= +invalid invalid if.wast:1376 AGFzbQEAAAABBAFgAAADAgEACg0BCwAEQAtBAUECGxoL +invalid invalid if.wast:1384 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABCg8CCAAEQAsQARoLBAAgAAs= +invalid invalid if.wast:1393 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQkHAQBBAAsBAAoVAgQAIAALDgACfwRAC0EAEQAAGgsL +invalid invalid if.wast:1409 AGFzbQEAAAABBAFgAAADAgEACg4BDAEBfwRACyEAIAAaCw== +invalid invalid if.wast:1418 AGFzbQEAAAABBAFgAAADAgEACgwBCgEBfwRACyIAGgs= +invalid invalid if.wast:1427 AGFzbQEAAAABBAFgAAADAgEABgYBfwFBAAsKDAEKAARACyQAIwAaCw== +invalid invalid if.wast:1436 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAARAC0AAGgs= +invalid invalid if.wast:1445 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAARACygCABoL +invalid invalid if.wast:1454 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDAEKAARAC0EBNgIACw== +invalid invalid if.wast:1464 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoKAQgAQQEEARoLCw== +invalid invalid if.wast:1470 AGFzbQEAAAABCQJgAABgAn98AAMCAQAKCwEJAEEBBAEaGgsL +invalid invalid if.wast:1476 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoPAQ0AQwAAAABBAQQBGgsL +invalid invalid if.wast:1482 AGFzbQEAAAABCQJgAABgAn1/AAMCAQAKEAEOAEMAAAAAQQEEARoaCws= +invalid invalid if.wast:1488 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoNAQsAAkBBAQQBGgsLCw== +invalid invalid if.wast:1494 AGFzbQEAAAABCQJgAABgAn98AAMCAQAKDgEMAAJAQQEEARoaCwsL +invalid invalid if.wast:1500 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoSARAAAkBDAAAAAEEBBAEaCwsL +invalid invalid if.wast:1506 AGFzbQEAAAABCQJgAABgAn1/AAMCAQAKEwERAAJAQwAAAABBAQQBGhoLCws= +valid valid imports.wast:3 AGFzbQEAAAABHgdgAABgAX8AYAF9AGAAAX9gAAF9YAF/AX9gAX4BfgMIBwABAgMEBQYECAJwAApwAQoUBQMBAAIGFAN/AEE3C30AQwAAMEILfgFCwgALB60BDQRmdW5jAAAIZnVuYy1pMzIAAQhmdW5jLWYzMgACCWZ1bmMtPmkzMgADCWZ1bmMtPmYzMgAEDWZ1bmMtaTMyLT5pMzIABQ1mdW5jLWk2NC0+aTY0AAYKZ2xvYmFsLWkzMgMACmdsb2JhbC1mMzIDAQ5nbG9iYWwtbXV0LWk2NAMCDHRhYmxlLTEwLWluZgEAC3RhYmxlLTEwLTIwAQEMbWVtb3J5LTItaW5mAgAKIQcCAAsCAAsCAAsEAEEWCwcAQwAAMEELBAAgAAsEACAACw== +valid valid imports.wast:26 AGFzbQEAAAABJAhgAX8AYAF+AGABfQBgAXwAYAF/AGACf30AYAJ8fABgAX4BfgKDAxIIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTY0AAEIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTY0AAEIc3BlY3Rlc3QJcHJpbnRfZjMyAAIIc3BlY3Rlc3QJcHJpbnRfZjY0AAMIc3BlY3Rlc3QNcHJpbnRfaTMyX2YzMgAFCHNwZWN0ZXN0DXByaW50X2Y2NF9mNjQABghzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9mNjQAAwR0ZXN0DWZ1bmMtaTY0LT5pNjQABwhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIABAhzcGVjdGVzdAlwcmludF9pMzIABAMDAgABBAUBcAECAgczCAJwMQALAnAyAAwCcDMADQJwNAANAnA1AA4CcDYADwdwcmludDMyABIHcHJpbnQ2NAATCQgBAEEACwICBQpoAiwBAX0gALIhASAAEAAgAEEBakMAAChCEAYgABACIAAQCCABEAQgAEEAEQAACzkBAXwgABAKuSEBIAAQASABRAAAAAAAAPA/oEQAAAAAAIBKQBAHIAAQAyABEAUgARAJIAFBAREDAAs= +invalid invalid imports.wast:89 AGFzbQEAAAABBQFgAAF/Ag0BBHRlc3QEZnVuYwAB +valid valid imports.wast:97 AGFzbQEAAAABBQFgAX8AAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAAcNAQlwcmludF9pMzIAAQoIAQYAIAAQAAs= +valid valid imports.wast:107 AGFzbQEAAAABCwJgAX8AYAJ/fwF/AhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQcNAQlwcmludF9pMzIAAQoJAQcAIAAgAWoL +valid valid imports.wast:116 AGFzbQEAAAABBAFgAAACDQEEdGVzdARmdW5jAAA= +valid valid imports.wast:117 AGFzbQEAAAABBQFgAX8AAhEBBHRlc3QIZnVuYy1pMzIAAA== +valid valid imports.wast:118 AGFzbQEAAAABBQFgAX0AAhEBBHRlc3QIZnVuYy1mMzIAAA== +valid valid imports.wast:119 AGFzbQEAAAABBQFgAAF/AhIBBHRlc3QJZnVuYy0+aTMyAAA= +valid valid imports.wast:120 AGFzbQEAAAABBQFgAAF9AhIBBHRlc3QJZnVuYy0+ZjMyAAA= +valid valid imports.wast:121 AGFzbQEAAAABBgFgAX8BfwIWAQR0ZXN0DWZ1bmMtaTMyLT5pMzIAAA== +valid valid imports.wast:122 AGFzbQEAAAABBgFgAX4BfgIWAQR0ZXN0DWZ1bmMtaTY0LT5pNjQAAA== +valid valid imports.wast:125 AGFzbQEAAAABBAFgAAACEAEEdGVzdAd1bmtub3duAAA= +valid valid imports.wast:129 AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QHdW5rbm93bgAA +valid valid imports.wast:134 AGFzbQEAAAABBQFgAX8AAg0BBHRlc3QEZnVuYwAA +valid valid imports.wast:138 AGFzbQEAAAABBQFgAAF/Ag0BBHRlc3QEZnVuYwAA +valid valid imports.wast:142 AGFzbQEAAAABBgFgAX8BfwINAQR0ZXN0BGZ1bmMAAA== +valid valid imports.wast:146 AGFzbQEAAAABBAFgAAACEQEEdGVzdAhmdW5jLWkzMgAA +valid valid imports.wast:150 AGFzbQEAAAABBQFgAAF/AhEBBHRlc3QIZnVuYy1pMzIAAA== +valid valid imports.wast:154 AGFzbQEAAAABBQFgAX0AAhEBBHRlc3QIZnVuYy1pMzIAAA== +valid valid imports.wast:158 AGFzbQEAAAABBQFgAX4AAhEBBHRlc3QIZnVuYy1pMzIAAA== +valid valid imports.wast:162 AGFzbQEAAAABBgFgAX8BfwIRAQR0ZXN0CGZ1bmMtaTMyAAA= +valid valid imports.wast:166 AGFzbQEAAAABBAFgAAACEgEEdGVzdAlmdW5jLT5pMzIAAA== +valid valid imports.wast:170 AGFzbQEAAAABBQFgAX8AAhIBBHRlc3QJZnVuYy0+aTMyAAA= +valid valid imports.wast:174 AGFzbQEAAAABBQFgAAF9AhIBBHRlc3QJZnVuYy0+aTMyAAA= +valid valid imports.wast:178 AGFzbQEAAAABBQFgAAF+AhIBBHRlc3QJZnVuYy0+aTMyAAA= +valid valid imports.wast:182 AGFzbQEAAAABBgFgAX8BfwISAQR0ZXN0CWZ1bmMtPmkzMgAA +valid valid imports.wast:186 AGFzbQEAAAABBAFgAAACFgEEdGVzdA1mdW5jLWkzMi0+aTMyAAA= +valid valid imports.wast:190 AGFzbQEAAAABBQFgAX8AAhYBBHRlc3QNZnVuYy1pMzItPmkzMgAA +valid valid imports.wast:194 AGFzbQEAAAABBQFgAAF/AhYBBHRlc3QNZnVuYy1pMzItPmkzMgAA +valid valid imports.wast:199 AGFzbQEAAAABBQFgAAF/AhMBBHRlc3QKZ2xvYmFsLWkzMgAA +valid valid imports.wast:203 AGFzbQEAAAABBAFgAAACFQEEdGVzdAx0YWJsZS0xMC1pbmYAAA== +valid valid imports.wast:207 AGFzbQEAAAABBAFgAAACFQEEdGVzdAxtZW1vcnktMi1pbmYAAA== +valid valid imports.wast:211 AGFzbQEAAAABBAFgAAACFwEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgAA +valid valid imports.wast:215 AGFzbQEAAAABBAFgAAACEgEIc3BlY3Rlc3QFdGFibGUAAA== +valid valid imports.wast:219 AGFzbQEAAAABBAFgAAACEwEIc3BlY3Rlc3QGbWVtb3J5AAA= +valid valid imports.wast:226 AGFzbQEAAAABEQRgAAF/YAABfmAAAX1gAAF8AqIBBwhzcGVjdGVzdApnbG9iYWxfaTMyA38ACHNwZWN0ZXN0Cmdsb2JhbF9pMzIDfwAIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdApnbG9iYWxfaTMyA38ACHNwZWN0ZXN0Cmdsb2JhbF9pNjQDfgAIc3BlY3Rlc3QKZ2xvYmFsX2YzMgN9AAhzcGVjdGVzdApnbG9iYWxfZjY0A3wAAwgHAAAAAAECAwc5BwVnZXQtMAAABWdldC0xAAEFZ2V0LXgAAgVnZXQteQADBWdldC00AAQFZ2V0LTUABQVnZXQtNgAGCiQHBAAjAAsEACMBCwQAIwILBAAjAwsEACMECwQAIwULBAAjBgs= +valid valid imports.wast:254 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38A +valid valid imports.wast:255 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA30A +valid valid imports.wast:256 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN+AQ== +valid valid imports.wast:259 AGFzbQEAAAACEQEEdGVzdAd1bmtub3duA38A +valid valid imports.wast:263 AGFzbQEAAAACFQEIc3BlY3Rlc3QHdW5rbm93bgN/AA== +valid valid imports.wast:268 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA34A +valid valid imports.wast:272 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA30A +valid valid imports.wast:276 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA3wA +valid valid imports.wast:280 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38B +valid valid imports.wast:284 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA38A +valid valid imports.wast:288 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA34A +valid valid imports.wast:292 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA3wA +valid valid imports.wast:296 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA30B +valid valid imports.wast:300 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN/AQ== +valid valid imports.wast:304 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN9AQ== +valid valid imports.wast:308 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN8AQ== +valid valid imports.wast:312 AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN+AA== +valid valid imports.wast:317 AGFzbQEAAAACDgEEdGVzdARmdW5jA38A +valid valid imports.wast:321 AGFzbQEAAAACFgEEdGVzdAx0YWJsZS0xMC1pbmYDfwA= +valid valid imports.wast:325 AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYDfwA= +valid valid imports.wast:329 AGFzbQEAAAACFwEIc3BlY3Rlc3QJcHJpbnRfaTMyA38A +valid valid imports.wast:333 AGFzbQEAAAACEwEIc3BlY3Rlc3QFdGFibGUDfwA= +valid valid imports.wast:337 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5A38A +valid valid imports.wast:344 AGFzbQEAAAABCgJgAAF/YAF/AX8CFQEIc3BlY3Rlc3QFdGFibGUBcAEKFAMEAwEAAAcIAQRjYWxsAAAJCAEAQQELAgECChMDBwAgABEAAAsEAEELCwQAQRYL +valid valid imports.wast:381 AGFzbQEAAAACJwIIc3BlY3Rlc3QFdGFibGUBcAAACHNwZWN0ZXN0BXRhYmxlAXAAAAQHAnAACnAACg== +valid valid imports.wast:388 AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAK +valid valid imports.wast:389 AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAF +valid valid imports.wast:390 AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAA +valid valid imports.wast:391 AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAo= +valid valid imports.wast:392 AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAU= +valid valid imports.wast:393 AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAA= +valid valid imports.wast:394 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoU +valid valid imports.wast:395 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQUU +valid valid imports.wast:396 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQAU +valid valid imports.wast:397 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoZ +valid valid imports.wast:398 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQUZ +valid valid imports.wast:399 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQAZ +valid valid imports.wast:400 AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAK +valid valid imports.wast:401 AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAF +valid valid imports.wast:402 AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAA +valid valid imports.wast:403 AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKFA== +valid valid imports.wast:404 AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEFFA== +valid valid imports.wast:405 AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAFA== +valid valid imports.wast:406 AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKGQ== +valid valid imports.wast:407 AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEFGQ== +valid valid imports.wast:410 AGFzbQEAAAACEgEEdGVzdAd1bmtub3duAXAACg== +valid valid imports.wast:414 AGFzbQEAAAACFgEIc3BlY3Rlc3QHdW5rbm93bgFwAAo= +valid valid imports.wast:419 AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAM +valid valid imports.wast:423 AGFzbQEAAAACGAEEdGVzdAx0YWJsZS0xMC1pbmYBcAEKFA== +valid valid imports.wast:427 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQwU +valid valid imports.wast:431 AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoS +valid valid imports.wast:435 AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAM +valid valid imports.wast:439 AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKDw== +valid valid imports.wast:444 AGFzbQEAAAACDwEEdGVzdARmdW5jAXAACg== +valid valid imports.wast:448 AGFzbQEAAAACFQEEdGVzdApnbG9iYWwtaTMyAXAACg== +valid valid imports.wast:452 AGFzbQEAAAACFwEEdGVzdAxtZW1vcnktMi1pbmYBcAAK +valid valid imports.wast:456 AGFzbQEAAAACGAEIc3BlY3Rlc3QJcHJpbnRfaTMyAXAACg== +valid valid imports.wast:464 AGFzbQEAAAABBgFgAX8BfwIVAQhzcGVjdGVzdAZtZW1vcnkCAQECAwIBAAcIAQRsb2FkAAAKCQEHACAAKAIACwsHAQBBCgsBEA== +invalid valid imports.wast:488 AGFzbQEAAAACCwIAAAIAAQAAAgAB +invalid valid imports.wast:492 AGFzbQEAAAACBgEAAAIAAQUDAQAA +invalid valid imports.wast:496 AGFzbQEAAAAFBQIAAAAA +valid valid imports.wast:500 AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAI= +valid valid imports.wast:501 AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAE= +valid valid imports.wast:502 AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAA= +valid valid imports.wast:503 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAB +valid valid imports.wast:504 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAA +valid valid imports.wast:505 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAg== +valid valid imports.wast:506 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAg== +valid valid imports.wast:507 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAw== +valid valid imports.wast:508 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAw== +valid valid imports.wast:511 AGFzbQEAAAACEQEEdGVzdAd1bmtub3duAgAB +valid valid imports.wast:515 AGFzbQEAAAACFQEIc3BlY3Rlc3QHdW5rbm93bgIAAQ== +valid valid imports.wast:520 AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAM= +valid valid imports.wast:524 AGFzbQEAAAACFwEEdGVzdAxtZW1vcnktMi1pbmYCAQID +valid valid imports.wast:528 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAC +valid valid imports.wast:532 AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAQ== +valid valid imports.wast:537 AGFzbQEAAAACEgEEdGVzdAhmdW5jLWkzMgIAAQ== +valid valid imports.wast:541 AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyAgAB +valid valid imports.wast:545 AGFzbQEAAAACFgEEdGVzdAx0YWJsZS0xMC1pbmYCAAE= +valid valid imports.wast:549 AGFzbQEAAAACFwEIc3BlY3Rlc3QJcHJpbnRfaTMyAgAB +valid valid imports.wast:553 AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgIAAQ== +valid valid imports.wast:557 AGFzbQEAAAACEwEIc3BlY3Rlc3QFdGFibGUCAAE= +valid valid imports.wast:570 AGFzbQEAAAABBgFgAX8BfwIVAQhzcGVjdGVzdAZtZW1vcnkCAQADAwIBAAcIAQRncm93AAAKCAEGACAAQAAL +valid valid imports.wast:657 AGFzbQEAAAABKQpgAABgAX8AYAJ/fwBgAX4AYAF9AGABfABgAAF/YAABfmAAAX1gAAF8AugCEAhub3Qgd2FzbQpvdmVybG9hZGVkAAAIbm90IHdhc20Kb3ZlcmxvYWRlZAABCG5vdCB3YXNtCm92ZXJsb2FkZWQAAghub3Qgd2FzbQpvdmVybG9hZGVkAAMIbm90IHdhc20Kb3ZlcmxvYWRlZAAECG5vdCB3YXNtCm92ZXJsb2FkZWQABQhub3Qgd2FzbQpvdmVybG9hZGVkAAYIbm90IHdhc20Kb3ZlcmxvYWRlZAAHCG5vdCB3YXNtCm92ZXJsb2FkZWQACAhub3Qgd2FzbQpvdmVybG9hZGVkAAkIbm90IHdhc20Kb3ZlcmxvYWRlZAN/AAhub3Qgd2FzbQpvdmVybG9hZGVkA34ACG5vdCB3YXNtCm92ZXJsb2FkZWQDfQAIbm90IHdhc20Kb3ZlcmxvYWRlZAN8AAhub3Qgd2FzbQpvdmVybG9hZGVkAXAAAAhub3Qgd2FzbQpvdmVybG9hZGVkAgAA +valid valid inline-module.wast:1 AGFzbQEAAAABBAFgAAADAwIAAAUDAQAABwUBAWYAAQoHAgIACwIACw== +valid valid int_exprs.wast:6 AGFzbQEAAAABDQJgAn9/AX9gAn5+AX8DBQQAAAEBB20EGGkzMi5ub19mb2xkX2NtcF9zX29mZnNldAAAGGkzMi5ub19mb2xkX2NtcF91X29mZnNldAABGGk2NC5ub19mb2xkX2NtcF9zX29mZnNldAACGGk2NC5ub19mb2xkX2NtcF91X29mZnNldAADCjkEDQAgAEEBaiABQQFqSAsNACAAQQFqIAFBAWpJCw0AIABCAXwgAUIBfFMLDQAgAEIBfCABQgF8VAs= +valid valid int_exprs.wast:25 AGFzbQEAAAABBgFgAX4BfgMCAQAHHQEZaTY0Lm5vX2ZvbGRfd3JhcF9leHRlbmRfcwAACggBBgAgAKesCw== +valid valid int_exprs.wast:35 AGFzbQEAAAABBgFgAX4BfgMCAQAHHQEZaTY0Lm5vX2ZvbGRfd3JhcF9leHRlbmRfdQAACggBBgAgAKetCw== +valid valid int_exprs.wast:44 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9zaGxfc2hyX3MAABVpMzIubm9fZm9sZF9zaGxfc2hyX3UAARVpNjQubm9fZm9sZF9zaGxfc2hyX3MAAhVpNjQubm9fZm9sZF9zaGxfc2hyX3UAAwotBAoAIABBAXRBAXULCgAgAEEBdEEBdgsKACAAQgGGQgGHCwoAIABCAYZCAYgL +valid valid int_exprs.wast:63 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9zaHJfc19zaGwAABVpMzIubm9fZm9sZF9zaHJfdV9zaGwAARVpNjQubm9fZm9sZF9zaHJfc19zaGwAAhVpNjQubm9fZm9sZF9zaHJfdV9zaGwAAwotBAoAIABBAXVBAXQLCgAgAEEBdkEBdAsKACAAQgGHQgGGCwoAIABCAYhCAYYL +valid valid int_exprs.wast:82 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9kaXZfc19tdWwAABVpMzIubm9fZm9sZF9kaXZfdV9tdWwAARVpNjQubm9fZm9sZF9kaXZfc19tdWwAAhVpNjQubm9fZm9sZF9kaXZfdV9tdWwAAwotBAoAIABBBm1BBmwLCgAgAEEGbkEGbAsKACAAQgZ/QgZ+CwoAIABCBoBCBn4L +valid valid int_exprs.wast:101 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdlBBZpMzIubm9fZm9sZF9kaXZfc19zZWxmAAAWaTMyLm5vX2ZvbGRfZGl2X3Vfc2VsZgABFmk2NC5ub19mb2xkX2Rpdl9zX3NlbGYAAhZpNjQubm9fZm9sZF9kaXZfdV9zZWxmAAMKIQQHACAAIABtCwcAIAAgAG4LBwAgACAAfwsHACAAIACACw== +valid valid int_exprs.wast:120 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdlBBZpMzIubm9fZm9sZF9yZW1fc19zZWxmAAAWaTMyLm5vX2ZvbGRfcmVtX3Vfc2VsZgABFmk2NC5ub19mb2xkX3JlbV9zX3NlbGYAAhZpNjQubm9fZm9sZF9yZW1fdV9zZWxmAAMKIQQHACAAIABvCwcAIAAgAHALBwAgACAAgQsHACAAIACCCw== +valid valid int_exprs.wast:139 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9tdWxfZGl2X3MAABVpMzIubm9fZm9sZF9tdWxfZGl2X3UAARVpNjQubm9fZm9sZF9tdWxfZGl2X3MAAhVpNjQubm9fZm9sZF9tdWxfZGl2X3UAAwotBAoAIABBBmxBBm0LCgAgAEEGbEEGbgsKACAAQgZ+QgZ/CwoAIABCBn5CBoAL +valid valid int_exprs.wast:158 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLQITaTMyLm5vX2ZvbGRfZGl2X3NfMgAAE2k2NC5ub19mb2xkX2Rpdl9zXzIAAQoRAgcAIABBAm0LBwAgAEICfws= +valid valid int_exprs.wast:171 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLQITaTMyLm5vX2ZvbGRfcmVtX3NfMgAAE2k2NC5ub19mb2xkX3JlbV9zXzIAAQoRAgcAIABBAm8LBwAgAEICgQs= +valid valid int_exprs.wast:184 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfMAAAC2kzMi5kaXZfdV8wAAELaTY0LmRpdl9zXzAAAgtpNjQuZGl2X3VfMAADCiEEBwAgAEEAbQsHACAAQQBuCwcAIABCAH8LBwAgAEIAgAs= +valid valid int_exprs.wast:203 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfMwAAC2kzMi5kaXZfdV8zAAELaTY0LmRpdl9zXzMAAgtpNjQuZGl2X3VfMwADCiEEBwAgAEEDbQsHACAAQQNuCwcAIABCA38LBwAgAEIDgAs= +valid valid int_exprs.wast:226 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfNQAAC2kzMi5kaXZfdV81AAELaTY0LmRpdl9zXzUAAgtpNjQuZGl2X3VfNQADCiEEBwAgAEEFbQsHACAAQQVuCwcAIABCBX8LBwAgAEIFgAs= +valid valid int_exprs.wast:249 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfNwAAC2kzMi5kaXZfdV83AAELaTY0LmRpdl9zXzcAAgtpNjQuZGl2X3VfNwADCiEEBwAgAEEHbQsHACAAQQduCwcAIABCB38LBwAgAEIHgAs= +valid valid int_exprs.wast:272 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfMwAAC2kzMi5yZW1fdV8zAAELaTY0LnJlbV9zXzMAAgtpNjQucmVtX3VfMwADCiEEBwAgAEEDbwsHACAAQQNwCwcAIABCA4ELBwAgAEIDggs= +valid valid int_exprs.wast:295 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfNQAAC2kzMi5yZW1fdV81AAELaTY0LnJlbV9zXzUAAgtpNjQucmVtX3VfNQADCiEEBwAgAEEFbwsHACAAQQVwCwcAIABCBYELBwAgAEIFggs= +valid valid int_exprs.wast:318 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfNwAAC2kzMi5yZW1fdV83AAELaTY0LnJlbV9zXzcAAgtpNjQucmVtX3VfNwADCiEEBwAgAEEHbwsHACAAQQdwCwcAIABCB4ELBwAgAEIHggs= +valid valid int_exprs.wast:341 AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLwIUaTMyLm5vX2ZvbGRfZGl2X25lZzEAABRpNjQubm9fZm9sZF9kaXZfbmVnMQABChECBwAgAEF/bQsHACAAQn9/Cw== +valid valid int_literals.wast:1 AGFzbQEAAAABCQJgAAF/YAABfgMfHgAAAAAAAAAAAAAAAQEBAQEBAQEBAQEAAAAAAQEBAQe3Ax4IaTMyLnRlc3QAAAhpMzIudW1heAABCGkzMi5zbWF4AAIMaTMyLm5lZ19zbWF4AAMIaTMyLnNtaW4ABAxpMzIuYWx0X3NtaW4ABQxpMzIuaW5jX3NtaW4ABgxpMzIubmVnX3plcm8ABw1pMzIubm90X29jdGFsAAgUaTMyLnVuc2lnbmVkX2RlY2ltYWwACQ1pMzIucGx1c19zaWduAAoIaTY0LnRlc3QACwhpNjQudW1heAAMCGk2NC5zbWF4AA0MaTY0Lm5lZ19zbWF4AA4IaTY0LnNtaW4ADwxpNjQuYWx0X3NtaW4AEAxpNjQuaW5jX3NtaW4AEQxpNjQubmVnX3plcm8AEg1pNjQubm90X29jdGFsABMUaTY0LnVuc2lnbmVkX2RlY2ltYWwAFA1pNjQucGx1c19zaWduABUMaTMyLWRlYy1zZXAxABYMaTMyLWRlYy1zZXAyABcMaTMyLWhleC1zZXAxABgMaTMyLWhleC1zZXAyABkMaTY0LWRlYy1zZXAxABoMaTY0LWRlYy1zZXAyABsMaTY0LWhleC1zZXAxABwMaTY0LWhleC1zZXAyAB0KlQIeCQBBjaC33QAPCwUAQX8PCwkAQf////8HDwsJAEGBgICAeA8LCQBBgICAgHgPCwkAQYCAgIB4DwsMAEGAgICAeEEBag8LBQBBAA8LBQBBCg8LBQBBfw8LBQBBKg8LDQBC7tSZ3eDN7tUMDwsFAEJ/DwsOAEL///////////8ADwsOAEKBgICAgICAgIB/DwsOAEKAgICAgICAgIB/DwsOAEKAgICAgICAgIB/DwsRAEKAgICAgICAgIB/QgF8DwsFAEIADwsFAEIKDwsFAEJ/DwsFAEIqDwsGAEHAhD0LBQBB6AcLCABBmYG80AALBgBBj9QGCwYAQsCEPQsFAELoBwsLAEKZs4KA8IG8BQsGAEKP1AYL +valid valid labels.wast:1 AGFzbQEAAAABCgJgAAF/YAF/AX8DExIAAAAAAQAAAAABAQAAAAAAAAAHmgESBWJsb2NrAAAFbG9vcDEAAQVsb29wMgACBWxvb3AzAAMFbG9vcDQABAVsb29wNQAFBWxvb3A2AAYCaWYABwNpZjIACAZzd2l0Y2gACQZyZXR1cm4ACgZicl9pZjAACwZicl9pZjEADAZicl9pZjIADQZicl9pZjMADgJicgAPCXNoYWRvd2luZwAQDHJlZGVmaW5pdGlvbgARCoMGEgsAAn9BAQwAQQALCyMBAX9BACEAAn8DfyAAQQFqIQAgAEEFRgRAIAAMAgsMAAsLCzQBAX9BACEAAn8DfyAAQQFqIQAgAEEFRgRADAELIABBCEYEQCAADAILIABBAWohAAwACwsLIwEBf0EAIQACfwN/IABBAWohACAAQQVGBEAgAAwCCyAACwsLIwEBf0EBIQECfwN/IAEgAWohASABIABLBEAgAQwCCwwACwsLCgADf0EBC0EBagsLAAN/QQANAEEDCwuEAQEBf0EAIQACQEEBBEAMAEGaBSEACyAAQQFqIQBBAQRADABBmgUhAAVB+AYhAAsgAEEBaiEAQQEEQAwAQZoFIQAFQfgGIQALIABBAWohAEEABEBB+AYhAAUMAEGaBSEACyAAQQFqIQBBAARAQfgGIQAFDABBmgUhAAsgAEEBaiEACyAAC4QBAQF/QQAhAAJAQQEEQAwAQZoFIQALIABBAWohAEEBBEAMAEGaBSEABUH4BiEACyAAQQFqIQBBAQRADABBmgUhAAVB+AYhAAsgAEEBaiEAQQAEQEH4BiEABQwAQZoFIQALIABBAWohAEEABEBB+AYhAAUMAEGaBSEACyAAQQFqIQALIAALLQACf0EKAn8CQAJAAkACQAJAIAAOBAQAAQIDCwtBAgwDC0EDDAMLC0EFC2wLCxgAAkACQAJAIAAOAQABDAILQQAPCwtBAgtWAQF/QQAhAAJ/AkBBAA0AIABBAXIhAEEBDQAgAEECciEACwJ/IABBBHIhACAAC0EADQAaIABBCHIhAAJ/IABBEHIhACAAC0EBDQAaIABBIHIhACAACwsTAAJ/An9BAQwAC0EBDQAaQQALCxgAAn9BAQRAAn9BAQwAC0EBDQEaC0EACwskAQF/An8Cf0EBIQAgAAsCf0ECIQAgAAsNABpBAAtBAGoaIAALIQACf0EBBEACf0EBDAALDAEFAkACf0EBDAALGgsLQQELCwwAAn9BAQwAQQJzCwsSAAJ/An9BAgsCf0EDDAALagsL +invalid invalid labels.wast:318 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEBDQCMAQsL +invalid invalid labels.wast:322 AGFzbQEAAAABBAFgAAADAgEAChABDgACQEMAAAAAQQENAAsL +valid valid left-to-right.wast:1 AGFzbQEAAAABQA1gAn9/AX9gAn5+AX9gAn19AX9gAnx8AX9gAABgAAF/YAABfmAAAX1gAAF8YAJ/fwBgAn5+AGACfX0AYAJ8fAADhAGCAQAAAQECAgMDBAQFBQUFBQUGBgYFBQcHBwUFCAgIBQUJCgsMBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEBQFwAQgIBQMBAAEHqghfB2kzMl9hZGQAIwdpMzJfc3ViACQHaTMyX211bAAlCWkzMl9kaXZfcwAmCWkzMl9kaXZfdQAnCWkzMl9yZW1fcwAoCWkzMl9yZW1fdQApB2kzMl9hbmQAKgZpMzJfb3IAKwdpMzJfeG9yACwHaTMyX3NobAAtCWkzMl9zaHJfdQAuCWkzMl9zaHJfcwAvBmkzMl9lcQAwBmkzMl9uZQAxCGkzMl9sdF9zADIIaTMyX2xlX3MAMwhpMzJfbHRfdQA0CGkzMl9sZV91ADUIaTMyX2d0X3MANghpMzJfZ2VfcwA3CGkzMl9ndF91ADgIaTMyX2dlX3UAOQlpMzJfc3RvcmUAOgppMzJfc3RvcmU4ADsLaTMyX3N0b3JlMTYAPAhpMzJfY2FsbAA9EWkzMl9jYWxsX2luZGlyZWN0AD4KaTMyX3NlbGVjdAA/B2k2NF9hZGQAQAdpNjRfc3ViAEEHaTY0X211bABCCWk2NF9kaXZfcwBDCWk2NF9kaXZfdQBECWk2NF9yZW1fcwBFCWk2NF9yZW1fdQBGB2k2NF9hbmQARwZpNjRfb3IASAdpNjRfeG9yAEkHaTY0X3NobABKCWk2NF9zaHJfdQBLCWk2NF9zaHJfcwBMBmk2NF9lcQBNBmk2NF9uZQBOCGk2NF9sdF9zAE8IaTY0X2xlX3MAUAhpNjRfbHRfdQBRCGk2NF9sZV91AFIIaTY0X2d0X3MAUwhpNjRfZ2VfcwBUCGk2NF9ndF91AFUIaTY0X2dlX3UAVglpNjRfc3RvcmUAVwppNjRfc3RvcmU4AFgLaTY0X3N0b3JlMTYAWQtpNjRfc3RvcmUzMgBaCGk2NF9jYWxsAFsRaTY0X2NhbGxfaW5kaXJlY3QAXAppNjRfc2VsZWN0AF0HZjMyX2FkZABeB2YzMl9zdWIAXwdmMzJfbXVsAGAHZjMyX2RpdgBhDGYzMl9jb3B5c2lnbgBiBmYzMl9lcQBjBmYzMl9uZQBkBmYzMl9sdABlBmYzMl9sZQBmBmYzMl9ndABnBmYzMl9nZQBoB2YzMl9taW4AaQdmMzJfbWF4AGoJZjMyX3N0b3JlAGsIZjMyX2NhbGwAbBFmMzJfY2FsbF9pbmRpcmVjdABtCmYzMl9zZWxlY3QAbgdmNjRfYWRkAG8HZjY0X3N1YgBwB2Y2NF9tdWwAcQdmNjRfZGl2AHIMZjY0X2NvcHlzaWduAHMGZjY0X2VxAHQGZjY0X25lAHUGZjY0X2x0AHYGZjY0X2xlAHcGZjY0X2d0AHgGZjY0X2dlAHkHZjY0X21pbgB6B2Y2NF9tYXgAewlmNjRfc3RvcmUAfAhmNjRfY2FsbAB9EWY2NF9jYWxsX2luZGlyZWN0AH4KZjY0X3NlbGVjdAB/BWJyX2lmAIABCGJyX3RhYmxlAIEBCQ4BAEEACwgAAQIDBAUGBwqqDYIBBABBfwsEAEF+CwQAQX8LBABBfgsEAEF/CwQAQX4LBABBfwsEAEF+CwkAQQhBADYCAAsnAEELQQotAAA6AABBCkEJLQAAOgAAQQlBCC0AADoAAEEIQX06AAALBwBBCCgCAAsNABAJQQhBAToAAEEACw0AEAlBCEECOgAAQQELDQAQCUEIQQM6AABBAQsNABAJQQhBBDoAAEEACw0AEAlBCEEFOgAAQQALDQAQCUEIQQE6AABCAAsNABAJQQhBAjoAAEIBCw0AEAlBCEEDOgAAQgELDQAQCUEIQQQ6AABBAgsNABAJQQhBBToAAEEACxAAEAlBCEEBOgAAQwAAAAALEAAQCUEIQQI6AABDAACAPwsQABAJQQhBAzoAAEMAAIA/Cw0AEAlBCEEEOgAAQQQLDQAQCUEIQQU6AABBAAsUABAJQQhBAToAAEQAAAAAAAAAAAsUABAJQQhBAjoAAEQAAAAAAADwPwsUABAJQQhBAzoAAEQAAAAAAADwPwsNABAJQQhBBDoAAEEGCw0AEAlBCEEFOgAAQQALAgALAgALAgALAgALDAAQCBALEAxqGhAKCwwAEAgQCxAMaxoQCgsMABAIEAsQDGwaEAoLDAAQCBALEAxtGhAKCwwAEAgQCxAMbhoQCgsMABAIEAsQDG8aEAoLDAAQCBALEAxwGhAKCwwAEAgQCxAMcRoQCgsMABAIEAsQDHIaEAoLDAAQCBALEAxzGhAKCwwAEAgQCxAMdBoQCgsMABAIEAsQDHYaEAoLDAAQCBALEAx1GhAKCwwAEAgQCxAMRhoQCgsMABAIEAsQDEcaEAoLDAAQCBALEAxIGhAKCwwAEAgQCxAMTBoQCgsMABAIEAsQDEkaEAoLDAAQCBALEAxNGhAKCwwAEAgQCxAMShoQCgsMABAIEAsQDE4aEAoLDAAQCBALEAxLGhAKCwwAEAgQCxAMTxoQCgsNABAIEAsQDDYCABAKCw0AEAgQCxAMOgAAEAoLDQAQCBALEAw7AQAQCgsMABAIEAsQDBAfEAoLEAAQCBALEAwQDhEAABoQCgsOABAIEAsQDBAPGxoQCgsMABAIEBAQEXwaEAoLDAAQCBAQEBF9GhAKCwwAEAgQEBARfhoQCgsMABAIEBAQEX8aEAoLDAAQCBAQEBGAGhAKCwwAEAgQEBARgRoQCgsMABAIEBAQEYIaEAoLDAAQCBAQEBGDGhAKCwwAEAgQEBARhBoQCgsMABAIEBAQEYUaEAoLDAAQCBAQEBGGGhAKCwwAEAgQEBARiBoQCgsMABAIEBAQEYcaEAoLDAAQCBAQEBFRGhAKCwwAEAgQEBARUhoQCgsMABAIEBAQEVMaEAoLDAAQCBAQEBFXGhAKCwwAEAgQEBARVBoQCgsMABAIEBAQEVgaEAoLDAAQCBAQEBFVGhAKCwwAEAgQEBARWRoQCgsMABAIEBAQEVYaEAoLDAAQCBAQEBFaGhAKCw0AEAgQCxARNwMAEAoLDQAQCBALEBE8AAAQCgsNABAIEAsQET0BABAKCw0AEAgQCxARPgIAEAoLDAAQCBAQEBEQIBAKCxAAEAgQEBAREBMRAQAaEAoLDgAQCBAQEBEQFBsaEAoLDAAQCBAVEBaSGhAKCwwAEAgQFRAWkxoQCgsMABAIEBUQFpQaEAoLDAAQCBAVEBaVGhAKCwwAEAgQFRAWmBoQCgsMABAIEBUQFlsaEAoLDAAQCBAVEBZcGhAKCwwAEAgQFRAWXRoQCgsMABAIEBUQFl8aEAoLDAAQCBAVEBZeGhAKCwwAEAgQFRAWYBoQCgsMABAIEBUQFpYaEAoLDAAQCBAVEBaXGhAKCw0AEAgQCxAWOAIAEAoLDAAQCBAVEBYQIRAKCxAAEAgQFRAWEBgRAgAaEAoLDgAQCBAVEBYQGRsaEAoLDAAQCBAaEBugGhAKCwwAEAgQGhAboRoQCgsMABAIEBoQG6IaEAoLDAAQCBAaEBujGhAKCwwAEAgQGhAbphoQCgsMABAIEBoQG2EaEAoLDAAQCBAaEBtiGhAKCwwAEAgQGhAbYxoQCgsMABAIEBoQG2UaEAoLDAAQCBAaEBtkGhAKCwwAEAgQGhAbZhoQCgsMABAIEBoQG6QaEAoLDAAQCBAaEBulGhAKCw0AEAgQCxAbOQMAEAoLDAAQCBAaEBsQIhAKCxAAEAgQGhAbEB0RAwAaEAoLDgAQCBAaEBsQHhsaEAoLEwACfxAIEAsQDEEAcQ0AGhAKCwsVAAJ/EAgCfxALEAwOAQEACxoQCgsL +valid valid linking.wast:3 AGFzbQEAAAABBQFgAAF/AwMCAAAHCAEEY2FsbAAACgsCBAAQAQsEAEECCw== +valid valid linking.wast:9 AGFzbQEAAAABBQFgAAF/AgsBAk1mBGNhbGwAAAMEAwAAAAchAwdNZi5jYWxsAAAMY2FsbCBNZi5jYWxsAAEEY2FsbAACChADBAAQAAsEABADCwQAQQML +valid valid linking.wast:22 AGFzbQEAAAABBQFgAX8AAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAABwkBBXByaW50AAA= +valid valid linking.wast:28 AGFzbQEAAAABBQFgAX4AAhQBCnJlZXhwb3J0X2YFcHJpbnQAAA== +valid valid linking.wast:32 AGFzbQEAAAABBgFgAX8BfwIUAQpyZWV4cG9ydF9mBXByaW50AAA= +valid valid linking.wast:39 AGFzbQEAAAABCQJgAAF/YAF/AAMEAwAAAQYMAn8AQSoLfwFBjgELBy0FBGdsb2IDAANnZXQAAAhtdXRfZ2xvYgMBB2dldF9tdXQAAQdzZXRfbXV0AAIKEgMEACMACwQAIwELBgAgACQBCw== +valid valid linking.wast:50 AGFzbQEAAAABCQJgAAF/YAF/AAI+BQJNZwRnbG9iA38AAk1nCG11dF9nbG9iA38BAk1nA2dldAAAAk1nB2dldF9tdXQAAAJNZwdzZXRfbXV0AAEDAgEABgYBfwBBKwsHSQcHTWcuZ2xvYgMABk1nLmdldAAABGdsb2IDAgNnZXQAAwtNZy5tdXRfZ2xvYgMBCk1nLmdldF9tdXQAAQpNZy5zZXRfbXV0AAIKBgEEACMCCw== +valid valid linking.wast:87 AGFzbQEAAAACEAECTWcIbXV0X2dsb2IDfwA= +valid valid linking.wast:91 AGFzbQEAAAACDAECTWcEZ2xvYgN/AQ== +valid valid linking.wast:96 AGFzbQEAAAAGFQRwANBwC3AB0HALbwDQbwtvAdBvCwc9BAxnLWNvbnN0LWZ1bmMDAApnLXZhci1mdW5jAwEOZy1jb25zdC1leHRlcm4DAgxnLXZhci1leHRlcm4DAw== +valid valid linking.wast:104 AGFzbQEAAAACYQQHTXJlZl9leAxnLWNvbnN0LWZ1bmMDcAAHTXJlZl9leA5nLWNvbnN0LWV4dGVybgNvAAdNcmVmX2V4CmctdmFyLWZ1bmMDcAEHTXJlZl9leAxnLXZhci1leHRlcm4DbwE= +valid valid linking.wast:113 AGFzbQEAAAACGwEHTXJlZl9leA5nLWNvbnN0LWV4dGVybgNwAA== +valid valid linking.wast:117 AGFzbQEAAAACGQEHTXJlZl9leAxnLWNvbnN0LWZ1bmMDbwA= +valid valid linking.wast:123 AGFzbQEAAAACFwEHTXJlZl9leApnLXZhci1mdW5jA28B +valid valid linking.wast:127 AGFzbQEAAAACGQEHTXJlZl9leAxnLXZhci1leHRlcm4DcAE= +valid valid linking.wast:134 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8DBAMAAAIEBAFwAAoHEgMDdGFiAQABaAABBGNhbGwAAgkKAQBBAgsEAAAAAAoTAwQAQQQLBABBfAsHACAAEQAACw== +valid valid linking.wast:149 AGFzbQEAAAABDQNgAABgAAF/YAF/AX8CEgICTXQEY2FsbAACAk10AWgAAQMEAwECAgQFAXABBQUHIQMHTXQuY2FsbAAADGNhbGwgTXQuY2FsbAADBGNhbGwABAkLAQBBAAsFAgICAQAKFQMEAEEFCwYAIAAQAAsHACAAEQEACw== +valid valid linking.wast:191 AGFzbQEAAAABCgJgAAF/YAF/AX8CEwICTXQBaAAAAk10A3RhYgFwAAUDAwIAAQcIAQRjYWxsAAIJCAEAQQELAgEACg4CBABBBgsHACAAEQAACw== +valid valid linking.wast:229 AGFzbQEAAAABBAFgAAACDAECTXQDdGFiAXAAAAMCAQAJBwEAQQkLAQAKBAECAAs= +valid valid linking.wast:235 AGFzbQEAAAAGBgF/AEEFCwcFAQFnAwA= +valid valid linking.wast:237 AGFzbQEAAAACCQECRzEBZwN/AAYGAX8AIwALBwUBAWcDAQ== +valid valid linking.wast:244 AGFzbQEAAAABBAFgAAACDAECTXQDdGFiAXAAAAMCAQAJBwEAQQoLAQAKBAECAAs= +valid valid linking.wast:253 AGFzbQEAAAABBQFgAAF/AhYCAk10A3RhYgFwAAoCTXQDbWVtAgABAwIBAAkNAgBBBwsBAABBCQsBAAoGAQQAQQAL +valid valid linking.wast:267 AGFzbQEAAAABBQFgAAF/AgwBAk10A3RhYgFwAAoDAgEACRECAEEHCwEAAEEICwUAAAAAAAoGAQQAQQAL +valid valid linking.wast:279 AGFzbQEAAAABBQFgAAF/AgwBAk10A3RhYgFwAAoDAgEABQMBAAEJBwEAQQcLAQAKBgEEAEEACwsJAQBBgIAECwFk +valid valid linking.wast:291 AGFzbQEAAAAEBwJwAAFvAAEHFQIGdC1mdW5jAQAIdC1leHRlcm4BAQ== +valid valid linking.wast:297 AGFzbQEAAAACLQIJTXRhYmxlX2V4BnQtZnVuYwFwAAEJTXRhYmxlX2V4CHQtZXh0ZXJuAW8AAQ== +valid valid linking.wast:303 AGFzbQEAAAACFgEJTXRhYmxlX2V4BnQtZnVuYwFvAAE= +valid valid linking.wast:307 AGFzbQEAAAACGAEJTXRhYmxlX2V4CHQtZXh0ZXJuAXAAAQ== +valid valid linking.wast:314 AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAQUHDgIDbWVtAgAEbG9hZAAACgkBBwAgAC0AAAsLEAEAQQoLCgABAgMEBQYHCAk= +valid valid linking.wast:324 AGFzbQEAAAABBgFgAX8BfwILAQJNbQRsb2FkAAADAgEABQMBAAEHEgIHTW0ubG9hZAAABGxvYWQAAQoJAQcAIAAtAAALCwwBAEEKCwbw8fLz9PU= +valid valid linking.wast:340 AGFzbQEAAAABBgFgAX8BfwILAQJNbQNtZW0CAAEDAgEABwgBBGxvYWQAAAoJAQcAIAAtAAALCw4BAEEFCwigoaKjpKWmpw== +valid valid linking.wast:354 AGFzbQEAAAACCwECTW0DbWVtAgAACwkBAEH//wMLAWE= +valid valid linking.wast:360 AGFzbQEAAAACCwECTW0DbWVtAgAACwkBAEGAgAQLAWE= +valid valid linking.wast:367 AGFzbQEAAAABBgFgAX8BfwIMAQJNbQNtZW0CAQEIAwIBAAcIAQRncm93AAAKCAEGACAAQAAL +valid valid linking.wast:385 AGFzbQEAAAABBAFgAAACJwMIc3BlY3Rlc3QFcHJpbnQAAAJNbQNtZW0CAAECTW0DdGFiAXAAAAsJAQBBAAsDYWJj +valid valid linking.wast:398 AGFzbQEAAAACCwECTW0DbWVtAgABCyICAEEACwNhYmMAQfb/EwsSenp6enp6enp6enp6enp6enp6 +valid valid linking.wast:410 AGFzbQEAAAABBAFgAAACCwECTW0DbWVtAgABAwIBAAQEAXAAAAkHAQBBAAsBAAoEAQIACwsJAQBBAAsDYWJj +valid valid linking.wast:422 AGFzbQEAAAABBQFgAAF/AwMCAAAEBAFwAAEFAwEAAQcxBAZtZW1vcnkCAAV0YWJsZQEADWdldCBtZW1vcnlbMF0AAAxnZXQgdGFibGVbMF0AAQoRAgcAQQAtAAALBwBBABEAAAs= +valid valid linking.wast:436 AGFzbQEAAAABCAJgAAF/YAAAAhsCAk1zBm1lbW9yeQIAAQJNcwV0YWJsZQFwAAEDAwIAAQgBAQkHAQBBAAsBAAoMAgYAQa29AwsDAAALCwsBAEEACwVoZWxsbw== +valid valid load.wast:3 AGFzbQEAAAABFQRgA39/fwF/YAABf2AAAGACf38BfwMnJgECAQECAQEBAQEBAwMBAAEBAQEBAQECAQIBAQICAgIBAQEBAQEBBAUBcAEBAQUDAQABBgYBfwFBAAsHtwUlC2FzLWJyLXZhbHVlAAANYXMtYnJfaWYtY29uZAABDmFzLWJyX2lmLXZhbHVlAAITYXMtYnJfaWYtdmFsdWUtY29uZAADEWFzLWJyX3RhYmxlLWluZGV4AAQRYXMtYnJfdGFibGUtdmFsdWUABRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAGD2FzLXJldHVybi12YWx1ZQAHCmFzLWlmLWNvbmQACAphcy1pZi10aGVuAAkKYXMtaWYtZWxzZQAKD2FzLXNlbGVjdC1maXJzdAALEGFzLXNlbGVjdC1zZWNvbmQADA5hcy1zZWxlY3QtY29uZAANDWFzLWNhbGwtZmlyc3QADwthcy1jYWxsLW1pZAAQDGFzLWNhbGwtbGFzdAARFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAEhRhcy1jYWxsX2luZGlyZWN0LW1pZAATFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAUFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAFRJhcy1sb2NhbC5zZXQtdmFsdWUAFhJhcy1sb2NhbC50ZWUtdmFsdWUAFxNhcy1nbG9iYWwuc2V0LXZhbHVlABgPYXMtbG9hZC1hZGRyZXNzABkQYXMtbG9hZE4tYWRkcmVzcwAaEGFzLXN0b3JlLWFkZHJlc3MAGw5hcy1zdG9yZS12YWx1ZQAcEWFzLXN0b3JlTi1hZGRyZXNzAB0PYXMtc3RvcmVOLXZhbHVlAB4QYXMtdW5hcnktb3BlcmFuZAAfDmFzLWJpbmFyeS1sZWZ0ACAPYXMtYmluYXJ5LXJpZ2h0ACEPYXMtdGVzdC1vcGVyYW5kACIPYXMtY29tcGFyZS1sZWZ0ACMQYXMtY29tcGFyZS1yaWdodAAkE2FzLW1lbW9yeS5ncm93LXNpemUAJQkHAQBBAAsBDgqGBCYMAAJ/QQAoAgAMAAsLDAACQEEAKAIADQALCxEAAn9BACgCAEEBDQAaQQcLCxEAAn9BBkEAKAIADQAaQQcLCw8AAkBBACgCAA4CAAAACwsTAAJ/QQAoAgBBAQ4CAAAAQQcLCxIAAn9BBkEAKAIADgEAAEEHCwsIAEEAKAIADwsPAEEAKAIABH9BAAVBAQsLDwBBAQR/QQAoAgAFQQALCw8AQQAEf0EABUEAKAIACwsMAEEAKAIAIAAgARsLDAAgAEEAKAIAIAEbCwwAQQBBAUEAKAIAGwsEAEF/Cw0AQQAoAgBBAkEDEA4LDQBBAUEAKAIAQQMQDgsNAEEBQQJBACgCABAOCxAAQQAoAgBBAkEDQQARAAALEABBAUEAKAIAQQNBABEAAAsQAEEBQQJBACgCAEEAEQAACxAAQQFBAkEDQQAoAgARAAALCwEBf0EAKAIAIQALCwEBf0EAKAIAIgALCwEBf0EAKAIAJAALCgBBACgCACgCAAsKAEEAKAIALAAACwwAQQAoAgBBBzYCAAsMAEECQQAoAgA2AgALDABBACwAAEEHOgAACwwAQQJBACgCADsBAAsJAEHkACgCAGcLCwBB5AAoAgBBCmoLCwBBCkHkACgCAGsLCQBB5AAoAgBFCwsAQeQAKAIAQQpMCwsAQQpB5AAoAgBHCwoAQeQAKAIAQAAL +invalid invalid load.wast:312 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAKAIACw== +invalid invalid load.wast:316 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEALAAACw== +invalid invalid load.wast:320 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEALQAACw== +invalid invalid load.wast:324 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEALgEACw== +invalid invalid load.wast:328 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEALwEACw== +invalid invalid load.wast:332 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAKQMACw== +invalid invalid load.wast:336 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAMAAACw== +invalid invalid load.wast:340 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAMQAACw== +invalid invalid load.wast:344 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAMgEACw== +invalid invalid load.wast:348 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAMwEACw== +invalid invalid load.wast:352 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEANAIACw== +invalid invalid load.wast:356 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEANQIACw== +invalid invalid load.wast:360 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAKgIACw== +invalid invalid load.wast:364 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEAKwMACw== +invalid invalid load.wast:371 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABCgwBCgBDAAAAACgCAAs= +invalid invalid load.wast:372 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABCgwBCgBDAAAAACwAAAs= +invalid invalid load.wast:373 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABCgwBCgBDAAAAAC0AAAs= +invalid invalid load.wast:374 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABCgwBCgBDAAAAAC4BAAs= +invalid invalid load.wast:375 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABCgwBCgBDAAAAAC8BAAs= +invalid invalid load.wast:376 AGFzbQEAAAABBQFgAAF+AwIBAAUDAQABCgwBCgBDAAAAACkDAAs= +invalid invalid load.wast:377 AGFzbQEAAAABBQFgAAF+AwIBAAUDAQABCgwBCgBDAAAAADAAAAs= +invalid invalid load.wast:378 AGFzbQEAAAABBQFgAAF+AwIBAAUDAQABCgwBCgBDAAAAADEAAAs= +invalid invalid load.wast:379 AGFzbQEAAAABBQFgAAF+AwIBAAUDAQABCgwBCgBDAAAAADIBAAs= +invalid invalid load.wast:380 AGFzbQEAAAABBQFgAAF+AwIBAAUDAQABCgwBCgBDAAAAADMBAAs= +invalid invalid load.wast:381 AGFzbQEAAAABBQFgAAF+AwIBAAUDAQABCgwBCgBDAAAAADQCAAs= +invalid invalid load.wast:382 AGFzbQEAAAABBQFgAAF+AwIBAAUDAQABCgwBCgBDAAAAADUCAAs= +invalid invalid load.wast:383 AGFzbQEAAAABBQFgAAF9AwIBAAUDAQABCgwBCgBDAAAAACoCAAs= +invalid invalid load.wast:384 AGFzbQEAAAABBQFgAAF8AwIBAAUDAQABCgwBCgBDAAAAACsDAAs= +invalid invalid load.wast:388 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCAEGACgCABoL +invalid invalid load.wast:397 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDQELAEEAAkAoAgAaCws= +invalid invalid load.wast:407 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDQELAEEAA0AoAgAaCws= +invalid invalid load.wast:417 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDwENAEEAQQAEQCgCABoLCw== +invalid invalid load.wast:427 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKEgEQAEEAQQAEf0EABSgCAAsaCw== +invalid invalid load.wast:437 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDwENAEEAAkAoAgAMABoLCw== +invalid invalid load.wast:447 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKEQEPAEEAAkAoAgBBAQ0AGgsL +invalid invalid load.wast:457 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKEAEOAEEAAkAoAgAOAAAaCws= +invalid invalid load.wast:467 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCQEHACgCAA8aCw== +invalid invalid load.wast:476 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDQELACgCAEEBQQIbGgs= +invalid invalid load.wast:485 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQMBAAAKDwIIACgCABABGgsEACAACw== +invalid invalid load.wast:495 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQUDAQAACQcBAEEACwEAChUCBAAgAAsOAAJ/KAIAQQARAAAaCws= +invalid invalid load.wast:512 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDgEMAQF/KAIAIQAgABoL +invalid invalid load.wast:522 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDAEKAQF/KAIAIgAaCw== +invalid invalid load.wast:532 AGFzbQEAAAABBAFgAAADAgEABQMBAAAGBgF/AUEACwoMAQoAKAIAJAAjABoL +invalid invalid load.wast:542 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIACgCAEAAGgs= +invalid invalid load.wast:551 AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJACgCACgCABoL +invalid invalid load.wast:560 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDAEKACgCAEEBNgIACw== +valid valid local_get.wast:3 AGFzbQEAAAABNgpgAAF/YAABfmAAAX1gAAF8YAF/AX9gAX4BfmABfQF9YAF8AXxgBX59fH9/AGAFfn18f38BfAMUEwABAgMEBQYHCAkEBAQEBAQEBAQHswITDnR5cGUtbG9jYWwtaTMyAAAOdHlwZS1sb2NhbC1pNjQAAQ50eXBlLWxvY2FsLWYzMgACDnR5cGUtbG9jYWwtZjY0AAMOdHlwZS1wYXJhbS1pMzIABA50eXBlLXBhcmFtLWk2NAAFDnR5cGUtcGFyYW0tZjMyAAYOdHlwZS1wYXJhbS1mNjQABwp0eXBlLW1peGVkAAgEcmVhZAAJDmFzLWJsb2NrLXZhbHVlAAoNYXMtbG9vcC12YWx1ZQALC2FzLWJyLXZhbHVlAAwOYXMtYnJfaWYtdmFsdWUADRNhcy1icl9pZi12YWx1ZS1jb25kAA4RYXMtYnJfdGFibGUtdmFsdWUADw9hcy1yZXR1cm4tdmFsdWUAEAphcy1pZi10aGVuABEKYXMtaWYtZWxzZQASCo4CEwYBAX8gAAsGAQF+IAALBgEBfSAACwYBAXwgAAsEACAACwQAIAALBAAgAAsEACAACywDAX0CfgF8IABQGiABjBogApoaIANFGiAERRogBYwaIAZQGiAHUBogCJoaCz8DAX0CfgF8QwAAsEAhBUIGIQZEAAAAAAAAIEAhCCAAuiABuyACIAO4IAS3IAW7IAa6IAe6IAigoKCgoKCgoAsHAAJ/IAALCwcAA38gAAsLCQACfyAADAALCwsAAn8gAEEBDQALCwsAAn8gACAADQALCx0AAkACQAJAIAAOAgABAkEADwtBAQ8LQQIPC0EDCwUAIAAPCwwAIAAEfyAABUEACwsMACAABH9BAQUgAAsL +invalid invalid local_get.wast:153 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcBAX0gAEUL +invalid invalid local_get.wast:157 AGFzbQEAAAABBQFgAAF8AwIBAAoLAQkCAXwBfiABmgs= +invalid invalid local_get.wast:169 AGFzbQEAAAABBgFgAX0BfwMCAQAKBwEFACAARQs= +invalid invalid local_get.wast:173 AGFzbQEAAAABBwFgAnx+AXwDAgEACgcBBQAgAZoL +invalid invalid local_get.wast:181 AGFzbQEAAAABBAFgAAADAgEACggBBgEBfyAACw== +invalid invalid local_get.wast:185 AGFzbQEAAAABBAFgAAADAgEACggBBgEBfiAACw== +invalid invalid local_get.wast:189 AGFzbQEAAAABBAFgAAADAgEACggBBgEBfSAACw== +invalid invalid local_get.wast:193 AGFzbQEAAAABBAFgAAADAgEACggBBgEBfCAACw== +invalid invalid local_get.wast:201 AGFzbQEAAAABBAFgAAADAgEACgsBCQIBfwF+IAMaCw== +invalid invalid local_get.wast:205 AGFzbQEAAAABBAFgAAADAgEACg4BDAIBfwF+IPek6gYaCw== +invalid invalid local_get.wast:210 AGFzbQEAAAABBgFgAn9+AAMCAQAKBwEFACACGgs= +invalid invalid local_get.wast:214 AGFzbQEAAAABBgFgAn9+AAMCAQAKCwEJACD38s7UAhoL +invalid invalid local_get.wast:219 AGFzbQEAAAABBQFgAX8AAwIBAAoLAQkCAX8BfiADGgs= +invalid invalid local_get.wast:223 AGFzbQEAAAABBQFgAX4AAwIBAAoOAQwCAX8BfiD3qJlmGgs= +valid valid local_set.wast:3 AGFzbQEAAAABJQdgAABgAX8AYAF+AGABfQBgAXwAYAV+fXx/fwBgBX59fH9/AX4DFBMAAAAAAQIDBAUGAQEBAQEBAQEBB7QCEw50eXBlLWxvY2FsLWkzMgAADnR5cGUtbG9jYWwtaTY0AAEOdHlwZS1sb2NhbC1mMzIAAg50eXBlLWxvY2FsLWY2NAADDnR5cGUtcGFyYW0taTMyAAQOdHlwZS1wYXJhbS1pNjQABQ50eXBlLXBhcmFtLWYzMgAGDnR5cGUtcGFyYW0tZjY0AAcKdHlwZS1taXhlZAAIBXdyaXRlAAkOYXMtYmxvY2stdmFsdWUACg1hcy1sb29wLXZhbHVlAAsLYXMtYnItdmFsdWUADA5hcy1icl9pZi12YWx1ZQANE2FzLWJyX2lmLXZhbHVlLWNvbmQADhFhcy1icl90YWJsZS12YWx1ZQAPD2FzLXJldHVybi12YWx1ZQAQCmFzLWlmLXRoZW4AEQphcy1pZi1lbHNlABIK0gITCAEBf0EAIQALCAEBfkIAIQALCwEBfUMAAAAAIQALDwEBfEQAAAAAAAAAACEACwYAQQohAAsGAEILIQALCQBDmpkxQSEACw0ARGZmZmZmZihAIQALQAMBfQJ+AXxCACEAQwAAAAAhAUQAAAAAAAAAACECQQAhA0EAIQRDAAAAACEFQgAhBkIAIQdEAAAAAAAAAAAhCAtPAwF9An4BfEOamZm+IQFBKCEDQXkhBEMAALBAIQVCBiEGRAAAAAAAACBAIQggALogAbsgAiADuCAEtyAFuyAGuiAHuiAIoKCgoKCgoKCwCwkAAkBBASEACwsJAANAQQMhAAsLCwACQEEJIQAMAAsLDQACQEEIIQBBAQ0ACwsNAAJAQQZBCSEADQALCw4AAkBBCiEAQQEOAAALCwcAQQchAA8LCwAgAARAQQMhAAsLDAAgAARABUEBIQALCw== +invalid invalid local_set.wast:148 AGFzbQEAAAABBAFgAAADAgEACgkBBwEBfwEhAAs= +invalid invalid local_set.wast:152 AGFzbQEAAAABBAFgAAADAgEACg0BCwEBf0MAAAAAIQAL +invalid invalid local_set.wast:156 AGFzbQEAAAABBAFgAAADAgEAChEBDwEBfUQAAAAAAAAAACEACw== +invalid invalid local_set.wast:160 AGFzbQEAAAABBAFgAAADAgEAChMBEQIBfAF+RAAAAAAAAAAAIQEL +invalid invalid local_set.wast:169 AGFzbQEAAAABBQFgAX8AAwIBAAoHAQUAASEACw== +invalid invalid local_set.wast:173 AGFzbQEAAAABBQFgAX8AAwIBAAoLAQkAQwAAAAAhAAs= +invalid invalid local_set.wast:177 AGFzbQEAAAABBQFgAX0AAwIBAAoPAQ0ARAAAAAAAAAAAIQAL +invalid invalid local_set.wast:181 AGFzbQEAAAABBgFgAnx+AAMCAQAKDwENAEQAAAAAAAAAACEBCw== +invalid invalid local_set.wast:186 AGFzbQEAAAABBQFgAX8AAwIBAAoGAQQAIQAL +invalid invalid local_set.wast:194 AGFzbQEAAAABBQFgAX8AAwIBAAoLAQkAQQACQCEACws= +invalid invalid local_set.wast:203 AGFzbQEAAAABBQFgAX8AAwIBAAoLAQkAQQADQCEACws= +invalid invalid local_set.wast:212 AGFzbQEAAAABBQFgAX8AAwIBAAoNAQsAQQBBAQRAIQALCw== +invalid invalid local_set.wast:221 AGFzbQEAAAABBQFgAX8AAwIBAAoQAQ4AQQBBAAR/QQAFIQALCw== +invalid invalid local_set.wast:230 AGFzbQEAAAABBQFgAX8AAwIBAAoNAQsAQQACQCEADAALCw== +invalid invalid local_set.wast:239 AGFzbQEAAAABBQFgAX8AAwIBAAoNAQsAQQACQCEADQALCw== +invalid invalid local_set.wast:248 AGFzbQEAAAABBQFgAX8AAwIBAAoOAQwAQQACQCEADgAACws= +invalid invalid local_set.wast:257 AGFzbQEAAAABBQFgAX8AAwIBAAoHAQUAIQAPCw== +invalid invalid local_set.wast:265 AGFzbQEAAAABBQFgAX8AAwIBAAoLAQkAIQBBAUECGws= +invalid invalid local_set.wast:273 AGFzbQEAAAABCgJgAX8AYAF/AX8DAwIAAQoNAgYAIQAQAQsEACAACw== +invalid invalid local_set.wast:282 AGFzbQEAAAABCgJgAX8Bf2ABfwADAwIAAQQFAXABAQEJBwEAQQALAQAKEwIEACAACwwAAn8hAEEAEQAACws= +invalid invalid local_set.wast:301 AGFzbQEAAAABBQFgAX0AAwIBAAoNAQsBAX9DAAAAACEBCw== +invalid invalid local_set.wast:305 AGFzbQEAAAABBgFgAn5/AAMCAQAKDQELAQF9QwAAAAAhAQs= +invalid invalid local_set.wast:309 AGFzbQEAAAABBQFgAX4AAwIBAAoMAQoCAXwBfkIAIQEL +invalid invalid local_set.wast:317 AGFzbQEAAAABBgFgAX8BfwMCAQAKCAEGAEEBIQAL +invalid invalid local_set.wast:321 AGFzbQEAAAABBgFgAX4BfgMCAQAKCAEGAEIBIQAL +invalid invalid local_set.wast:325 AGFzbQEAAAABBgFgAX0BfQMCAQAKCwEJAEMAAIA/IQAL +invalid invalid local_set.wast:329 AGFzbQEAAAABBgFgAXwBfAMCAQAKDwENAEQAAAAAAADwPyEACw== +invalid invalid local_set.wast:337 AGFzbQEAAAABBAFgAAADAgEACgwBCgIBfwF+QQAhAws= +invalid invalid local_set.wast:341 AGFzbQEAAAABBAFgAAADAgEACg8BDQIBfwF+QQAh96TqBgs= +invalid invalid local_set.wast:346 AGFzbQEAAAABBgFgAn9+AAMCAQAKCAEGAEEAIQIL +invalid invalid local_set.wast:350 AGFzbQEAAAABBgFgAn9+AAMCAQAKDAEKAEEAIffyztQCCw== +invalid invalid local_set.wast:355 AGFzbQEAAAABBQFgAX8AAwIBAAoMAQoCAX8BfkEAIQML +invalid invalid local_set.wast:359 AGFzbQEAAAABBQFgAX4AAwIBAAoPAQ0CAX8BfkEAIfeomWYL +valid valid local_tee.wast:3 AGFzbQEAAAABWBBgA39/fwF/YAABf2AAAX5gAAF9YAABfGABfwF/YAF+AX5gAX0BfWABfAF8YAV+fXx/fwBgBX59fH9/AX5gBX59fH9/AXxgAABgAX8AYAJ/fwF/YAF+AX8DOjkBAgMEBQYHCAkKCwwFBQUFBQUFDQUFDQUFBQUFBQ4OBQAFBQUFBQUFDAUMBQUNDQ0NBwUFBQUFDwUEBQFwAQEBBQMBAAEGBgF/AUEACwfPBzcOdHlwZS1sb2NhbC1pMzIAAA50eXBlLWxvY2FsLWk2NAABDnR5cGUtbG9jYWwtZjMyAAIOdHlwZS1sb2NhbC1mNjQAAw50eXBlLXBhcmFtLWkzMgAEDnR5cGUtcGFyYW0taTY0AAUOdHlwZS1wYXJhbS1mMzIABg50eXBlLXBhcmFtLWY2NAAHCnR5cGUtbWl4ZWQACAV3cml0ZQAJBnJlc3VsdAAKDmFzLWJsb2NrLWZpcnN0AAwMYXMtYmxvY2stbWlkAA0NYXMtYmxvY2stbGFzdAAODWFzLWxvb3AtZmlyc3QADwthcy1sb29wLW1pZAAQDGFzLWxvb3AtbGFzdAARC2FzLWJyLXZhbHVlABINYXMtYnJfaWYtY29uZAATDmFzLWJyX2lmLXZhbHVlABQTYXMtYnJfaWYtdmFsdWUtY29uZAAVEWFzLWJyX3RhYmxlLWluZGV4ABYRYXMtYnJfdGFibGUtdmFsdWUAFxdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAYD2FzLXJldHVybi12YWx1ZQAZCmFzLWlmLWNvbmQAGgphcy1pZi10aGVuABsKYXMtaWYtZWxzZQAcD2FzLXNlbGVjdC1maXJzdAAdEGFzLXNlbGVjdC1zZWNvbmQAHg5hcy1zZWxlY3QtY29uZAAfDWFzLWNhbGwtZmlyc3QAIQthcy1jYWxsLW1pZAAiDGFzLWNhbGwtbGFzdAAjFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAJBRhcy1jYWxsX2luZGlyZWN0LW1pZAAlFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAmFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAJxJhcy1sb2NhbC5zZXQtdmFsdWUAKBJhcy1sb2NhbC50ZWUtdmFsdWUAKRNhcy1nbG9iYWwuc2V0LXZhbHVlACoPYXMtbG9hZC1hZGRyZXNzACsQYXMtbG9hZE4tYWRkcmVzcwAsEGFzLXN0b3JlLWFkZHJlc3MALQ5hcy1zdG9yZS12YWx1ZQAuEWFzLXN0b3JlTi1hZGRyZXNzAC8PYXMtc3RvcmVOLXZhbHVlADAQYXMtdW5hcnktb3BlcmFuZAAxDmFzLWJpbmFyeS1sZWZ0ADIPYXMtYmluYXJ5LXJpZ2h0ADMPYXMtdGVzdC1vcGVyYW5kADQPYXMtY29tcGFyZS1sZWZ0ADUQYXMtY29tcGFyZS1yaWdodAA2EmFzLWNvbnZlcnQtb3BlcmFuZAA3E2FzLW1lbW9yeS5ncm93LXNpemUAOAkHAQBBAAsBIAqDBzkIAQF/QQAiAAsIAQF+QgAiAAsLAQF9QwAAAAAiAAsPAQF8RAAAAAAAAAAAIgALBgBBCiIACwYAQgsiAAsJAEOamTFBIgALDQBEZmZmZmZmKEAiAAtSAwF9An4BfEIAIgBQGkMAAAAAIgGMGkQAAAAAAAAAACICmhpBACIDRRpBACIERRpDAAAAACIFjBpCACIGUBpCACIHUBpEAAAAAAAAAAAiCJoaC1UDAX0CfgF8Q5qZmb4iARpBKCIDGkF5IgQaQwAAsEAiBRpCBiIGGkQAAAAAAAAgQCIIGiAAuiABuyACIAO4IAS3IAW7IAa6IAe6IAigoKCgoKCgoLALTwMBfQJ+AXxCASIAukMAAABAIgG7RGZmZmZmZgpAIgJBBCIDuEEFIgS3QwAAsEAiBbtCBiIGukIAIge6RAAAAAAAACBAIgigoKCgoKCgoAsCAAsLAAJ/QQEiABALCwsNAAJ/EAtBASIAEAsLCw0AAn8QCxALQQEiAAsLCwADf0EDIgAQCwsLDQADfxALQQQiABALCwsNAAN/EAsQC0EFIgALCwsAAn9BCSIADAALCwsAAkBBASIADQALCxAAAn9BCCIAQQENABpBBwsLEAACf0EGQQkiAA0AGkEHCwsOAAJAQQAiAA4CAAAACwsSAAJ/QQoiAEEBDgIAAABBBwsLEQACf0EGQQsiAA4BAABBBwsLBwBBByIADwsOAEECIgAEf0EABUEBCwsOACAABH9BAyIABSAACwsOACAABH8gAAVBBCIACwsLAEEFIgAgACABGwsLACAAQQYiACABGwsLAEEAQQFBByIAGwsEAEF/CwwAQQwiAEECQQMQIAsMAEEBQQ0iAEEDECALDABBAUECQQ4iABAgCw8AQQEiAEECQQNBABEAAAsPAEEBQQIiAEEDQQARAAALDwBBAUECQQMiAEEAEQAACw8AQQFBAkEDQQAiABEAAAsKAQF/QQEiACEACwgAQQEiACIACwoBAX9BASIAJAALCQBBASIAKAIACwkAQQMiACwAAAsLAEEeIgBBBzYCAAsLAEECQQEiADYCAAsLAEEBIgBBBzoAAAsLAEECQQEiADsBAAsKAEPi8YB/IgCMCwkAQQMiAEEKagsJAEEKQQQiAGsLBwBBACIARQsJAEErIgBBCkwLCQBBCkEqIgBHCwcAQikiAKcLCABBKCIAQAAL +invalid invalid local_tee.wast:371 AGFzbQEAAAABBQFgAAF+AwIBAAoKAQgBAX9BACIACw== +invalid invalid local_tee.wast:375 AGFzbQEAAAABBAFgAAADAgEACg4BDAEBfUMAAAAAIgBFCw== +invalid invalid local_tee.wast:379 AGFzbQEAAAABBAFgAAADAgEACg0BCwIBfAF+QgAiAZoL +invalid invalid local_tee.wast:384 AGFzbQEAAAABBAFgAAADAgEACgkBBwEBfwEiAAs= +invalid invalid local_tee.wast:388 AGFzbQEAAAABBAFgAAADAgEACg0BCwEBf0MAAAAAIgAL +invalid invalid local_tee.wast:392 AGFzbQEAAAABBAFgAAADAgEAChEBDwEBfUQAAAAAAAAAACIACw== +invalid invalid local_tee.wast:396 AGFzbQEAAAABBAFgAAADAgEAChMBEQIBfAF+RAAAAAAAAAAAIgEL +invalid invalid local_tee.wast:417 AGFzbQEAAAABBQFgAX8AAwIBAAoHAQUAASIACw== +invalid invalid local_tee.wast:421 AGFzbQEAAAABBQFgAX8AAwIBAAoLAQkAQwAAAAAiAAs= +invalid invalid local_tee.wast:425 AGFzbQEAAAABBQFgAX0AAwIBAAoPAQ0ARAAAAAAAAAAAIgAL +invalid invalid local_tee.wast:429 AGFzbQEAAAABBgFgAnx+AAMCAQAKDwENAEQAAAAAAAAAACIBCw== +invalid invalid local_tee.wast:434 AGFzbQEAAAABBQFgAX8AAwIBAAoHAQUAIgAaCw== +invalid invalid local_tee.wast:442 AGFzbQEAAAABBQFgAX8AAwIBAAoMAQoAQQACQCIAGgsL +invalid invalid local_tee.wast:451 AGFzbQEAAAABBQFgAX8AAwIBAAoMAQoAQQADQCIAGgsL +invalid invalid local_tee.wast:460 AGFzbQEAAAABBQFgAX8AAwIBAAoOAQwAQQBBAARAIgAaCws= +invalid invalid local_tee.wast:469 AGFzbQEAAAABBQFgAX8AAwIBAAoRAQ8AQQBBAAR/QQAFIgALGgs= +invalid invalid local_tee.wast:478 AGFzbQEAAAABBQFgAX8AAwIBAAoOAQwAQQACQCIADAAaCws= +invalid invalid local_tee.wast:487 AGFzbQEAAAABBQFgAX8AAwIBAAoQAQ4AQQACQCIAQQENABoLCw== +invalid invalid local_tee.wast:496 AGFzbQEAAAABBQFgAX8AAwIBAAoPAQ0AQQACQCIADgAAGgsL +invalid invalid local_tee.wast:505 AGFzbQEAAAABBQFgAX8AAwIBAAoIAQYAIgAPGgs= +invalid invalid local_tee.wast:513 AGFzbQEAAAABBQFgAX8AAwIBAAoMAQoAIgBBAUECGxoL +invalid invalid local_tee.wast:521 AGFzbQEAAAABCgJgAX8AYAF/AX8DAwIAAQoOAgcAIgAQARoLBAAgAAs= +invalid invalid local_tee.wast:530 AGFzbQEAAAABCgJgAX8Bf2ABfwADAwIAAQQFAXABAQEJBwEAQQALAQAKFAIEACAACw0AAn8iAEEAEQAAGgsL +invalid invalid local_tee.wast:546 AGFzbQEAAAABBQFgAX8AAwIBAAoLAQkAIgAhACAAGgs= +invalid invalid local_tee.wast:554 AGFzbQEAAAABBQFgAX8AAwIBAAoJAQcAIgAiABoL +invalid invalid local_tee.wast:562 AGFzbQEAAAABBQFgAX8AAwIBAAYGAX8BQQALCgsBCQAiACQAIwAaCw== +invalid invalid local_tee.wast:571 AGFzbQEAAAABBQFgAX8AAwIBAAUDAQAACgkBBwAiAEAAGgs= +invalid invalid local_tee.wast:580 AGFzbQEAAAABBQFgAX8AAwIBAAUDAQAACgoBCAAiACgCABoL +invalid invalid local_tee.wast:589 AGFzbQEAAAABBQFgAX8AAwIBAAUDAQABCgsBCQAiAEEBNgIACw== +invalid invalid local_tee.wast:599 AGFzbQEAAAABBQFgAX0AAwIBAAoNAQsBAX9DAAAAACIBCw== +invalid invalid local_tee.wast:603 AGFzbQEAAAABBgFgAn5/AAMCAQAKDQELAQF9QwAAAAAiAQs= +invalid invalid local_tee.wast:607 AGFzbQEAAAABBQFgAX4AAwIBAAoMAQoCAXwBfkIAIgEL +invalid invalid local_tee.wast:615 AGFzbQEAAAABBAFgAAADAgEACg0BCwIBfwF+QQAiAxoL +invalid invalid local_tee.wast:619 AGFzbQEAAAABBAFgAAADAgEAChABDgIBfwF+QQAi96TqBhoL +invalid invalid local_tee.wast:624 AGFzbQEAAAABBgFgAn9+AAMCAQAKCQEHAEEAIgIaCw== +invalid invalid local_tee.wast:628 AGFzbQEAAAABBgFgAn9+AAMCAQAKDQELAEEAIvfyztQCGgs= +invalid invalid local_tee.wast:633 AGFzbQEAAAABBQFgAX8AAwIBAAoNAQsCAX8BfkEAIgMaCw== +invalid invalid local_tee.wast:637 AGFzbQEAAAABBQFgAX4AAwIBAAoQAQ4CAX8BfkEAIveomWYaCw== +valid valid loop.wast:3 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 +invalid invalid loop.wast:601 AGFzbQEAAAABBAFgAAADAgEACgkBBwADQEEACws= +invalid invalid loop.wast:609 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAA0ALCw== +invalid invalid loop.wast:613 AGFzbQEAAAABBQFgAAF+AwIBAAoHAQUAA0ALCw== +invalid invalid loop.wast:617 AGFzbQEAAAABBQFgAAF9AwIBAAoHAQUAA0ALCw== +invalid invalid loop.wast:621 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAA0ALCw== +invalid invalid loop.wast:626 AGFzbQEAAAABBAFgAAADAgEACgkBBwADQEEBCws= +invalid invalid loop.wast:632 AGFzbQEAAAABBAFgAAADAgEACgsBCQADQEEBQQILCw== +invalid invalid loop.wast:638 AGFzbQEAAAABBQFgAAF/AwIBAAoHAQUAA38LCw== +invalid invalid loop.wast:644 AGFzbQEAAAABBgFgAAJ/fwMCAQAKBwEFAAMACws= +invalid invalid loop.wast:650 AGFzbQEAAAABBQFgAAF/AwIBAAoIAQYAA38BCws= +invalid invalid loop.wast:656 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCAEGAAMAAQsL +invalid invalid loop.wast:662 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAA39DAAAAAAsL +invalid invalid loop.wast:668 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCQEHAAMAQQALCw== +invalid invalid loop.wast:674 AGFzbQEAAAABBgFgAAJ/fwMCAQAKCwEJAEEBAwBBAgsL +invalid invalid loop.wast:680 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAA39BAUECCws= +invalid invalid loop.wast:686 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAA34AAAAbCws= +invalid invalid loop.wast:693 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAAJAA38LGgsL +invalid invalid loop.wast:702 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAANAA38LGgsL +invalid invalid loop.wast:711 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEABEADfwsaCws= +invalid invalid loop.wast:721 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoIAQYAAwEaCws= +invalid invalid loop.wast:727 AGFzbQEAAAABCQJgAABgAn98AAMCAQAKCQEHAAMBGhoLCw== +invalid invalid loop.wast:733 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoNAQsAQwAAAAADARoLCw== +invalid invalid loop.wast:739 AGFzbQEAAAABCQJgAABgAn1/AAMCAQAKDgEMAEMAAAAAAwEaGgsL +invalid invalid loop.wast:745 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoLAQkAAkADARoLCws= +invalid invalid loop.wast:751 AGFzbQEAAAABCQJgAABgAn98AAMCAQAKDAEKAAJAAwEaGgsLCw== +invalid invalid loop.wast:757 AGFzbQEAAAABCAJgAABgAX8AAwIBAAoQAQ4AAkBDAAAAAAMBGgsLCw== +invalid invalid loop.wast:763 AGFzbQEAAAABCQJgAABgAn1/AAMCAQAKEQEPAAJAQwAAAAADARoaCwsL +valid valid memory.wast:3 AGFzbQEAAAAFAwEAAA== +valid valid memory.wast:4 AGFzbQEAAAAFAwEAAQ== +valid valid memory.wast:5 AGFzbQEAAAAFBAEBAAA= +valid valid memory.wast:6 AGFzbQEAAAAFBAEBAAE= +valid valid memory.wast:7 AGFzbQEAAAAFBQEBAYAC +valid valid memory.wast:8 AGFzbQEAAAAFBgEBAICABA== +invalid valid memory.wast:11 AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAABQMBAAA= +valid valid memory.wast:13 AGFzbQEAAAABBQFgAAF/AwIBAAUEAQEAAAcLAQdtZW1zaXplAAAKBgEEAD8ACwsGAQBBAAsA +valid valid memory.wast:17 AGFzbQEAAAABBQFgAAF/AwIBAAUEAQEBAQcLAQdtZW1zaXplAAAKBgEEAD8ACwsHAQBBAAsBeA== +invalid invalid memory.wast:22 AGFzbQEAAAALBwEAQQALAXg= +invalid invalid memory.wast:25 AGFzbQEAAAABBAFgAAADAgEACgoBCABBACoCABoL +invalid invalid memory.wast:29 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAEMAAAAAOAIACw== +invalid invalid memory.wast:33 AGFzbQEAAAABBAFgAAADAgEACgoBCABBACwAABoL +invalid invalid memory.wast:37 AGFzbQEAAAABBAFgAAADAgEACgsBCQBBAEEAOgAACw== +invalid invalid memory.wast:41 AGFzbQEAAAABBAFgAAADAgEACgcBBQA/ABoL +invalid invalid memory.wast:45 AGFzbQEAAAABBAFgAAADAgEACgkBBwBBAEAAGgs= +invalid invalid memory.wast:51 AGFzbQEAAAAFBAEBAQA= +invalid invalid memory.wast:55 AGFzbQEAAAAFBQEAgYAE +invalid invalid memory.wast:59 AGFzbQEAAAAFBwEAgICAgAg= +invalid invalid memory.wast:63 AGFzbQEAAAAFBwEA/////w8= +invalid invalid memory.wast:67 AGFzbQEAAAAFBgEBAIGABA== +invalid invalid memory.wast:71 AGFzbQEAAAAFCAEBAICAgIAI +invalid invalid memory.wast:75 AGFzbQEAAAAFCAEBAP////8P +valid valid memory.wast:92 AGFzbQEAAAABEwRgAAF/YAABfGABfwF/YAF+AX4DDQwAAQICAgIDAwMDAwMFAwEAAQehAQwEZGF0YQAABGNhc3QAAQtpMzJfbG9hZDhfcwACC2kzMl9sb2FkOF91AAMMaTMyX2xvYWQxNl9zAAQMaTMyX2xvYWQxNl91AAULaTY0X2xvYWQ4X3MABgtpNjRfbG9hZDhfdQAHDGk2NF9sb2FkMTZfcwAIDGk2NF9sb2FkMTZfdQAJDGk2NF9sb2FkMzJfcwAKDGk2NF9sb2FkMzJfdQALCp8CDE4AQQAtAABBwQBGQQMtAABBpwFGcUEGLQAAQQBGQRMtAABBAEZxcUEULQAAQdcARkEXLQAAQc0ARnFBGC0AAEEARkH/By0AAEEARnFxcQs4AEEIQseffzcDAEEIKwMAQseff79hBEBEAAAAAAAAAAAPC0EJQgA3AABBD0HFgAE7AABBCSsAAAsOAEEIIAA6AABBCCwAAAsOAEEIIAA6AABBCC0AAAsOAEEIIAA7AQBBCC4BAAsOAEEIIAA7AQBBCC8BAAsOAEEIIAA8AABBCDAAAAsOAEEIIAA8AABBCDEAAAsOAEEIIAA9AQBBCDIBAAsOAEEIIAA9AQBBCDMBAAsOAEEIIAA+AgBBCDQCAAsOAEEIIAA+AgBBCDUCAAsLFAIAQQALBUFCQ6dEAEEUCwRXQVNN +valid valid memory.wast:246 AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAQEGFgN/AEGQzgALfwBBkM4AC38AQZDOAAsHOgUGbWVtb3J5AgAKX19kYXRhX2VuZAMAC19fc3RhY2tfdG9wAwELX19oZWFwX2Jhc2UDAgRsb2FkAAAKCQEHACAALQAACw== +valid valid memory_copy.wast:6 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABCg0CAwABCwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:48 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDUECQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:90 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBGUEPQQL8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:132 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDUEZQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:174 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBFEEWQQT8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:216 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBGUEBQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:258 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBCkEMQQf8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:300 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDEEKQQf8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg== +valid valid memory_copy.wast:342 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsaAQBBAAsUAAECAwQFBgcICQoLDA0ODxAREhM= +valid valid memory_copy.wast:703 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsbAQBBAAsVAAECAwQFBgcICQoLDA0ODxAREhMU +valid valid memory_copy.wast:1065 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBB7P8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw== +valid valid memory_copy.wast:1426 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsdAQBB6/8DCxUAAQIDBAUGBwgJCgsMDQ4PEBESExQ= +valid valid memory_copy.wast:1788 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBBzv8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw== +valid valid memory_copy.wast:2510 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBB4v8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw== +valid valid memory_copy.wast:3593 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHFwMDbWVtAgADcnVuAAAHbG9hZDhfdQABChYCDAAgACABIAL8CgAACwcAIAAtAAALCxwBAEHs/wMLFAABAgMEBQYHCAkKCwwNDg8QERIT +valid valid memory_copy.wast:3954 AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBBgOADCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw== +invalid invalid memory_copy.wast:4316 AGFzbQEAAAABBAFgAAADAgEABwoBBnRlc3RmbgAACg4BDABBCkEUQR78CgAACw== +invalid invalid memory_copy.wast:4322 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBBCkEUQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4329 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg4BDABBCkEUQh78CgAACw== +invalid invalid memory_copy.wast:4336 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBBCkEURAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4343 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBBCkMAAKBBQR78CgAACw== +invalid invalid memory_copy.wast:4350 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBBCkMAAKBBQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4357 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBBCkMAAKBBQh78CgAACw== +invalid invalid memory_copy.wast:4364 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBBCkMAAKBBRAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4371 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg4BDABBCkIUQR78CgAACw== +invalid invalid memory_copy.wast:4378 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBBCkIUQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4385 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg4BDABBCkIUQh78CgAACw== +invalid invalid memory_copy.wast:4392 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBBCkIURAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4399 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBBCkQAAAAAAAA0QEEe/AoAAAs= +invalid invalid memory_copy.wast:4406 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBBCkQAAAAAAAA0QEMAAPBB/AoAAAs= +invalid invalid memory_copy.wast:4413 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBBCkQAAAAAAAA0QEIe/AoAAAs= +invalid invalid memory_copy.wast:4420 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChwBGgBBCkQAAAAAAAA0QEQAAAAAAAA+QPwKAAAL +invalid invalid memory_copy.wast:4427 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBDAAAgQUEUQR78CgAACw== +invalid invalid memory_copy.wast:4434 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBDAAAgQUEUQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4441 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBDAAAgQUEUQh78CgAACw== +invalid invalid memory_copy.wast:4448 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBDAAAgQUEURAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4455 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBDAAAgQUMAAKBBQR78CgAACw== +invalid invalid memory_copy.wast:4462 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBDAAAgQUMAAKBBQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4469 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBDAAAgQUMAAKBBQh78CgAACw== +invalid invalid memory_copy.wast:4476 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBDAAAgQUMAAKBBRAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4483 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBDAAAgQUIUQR78CgAACw== +invalid invalid memory_copy.wast:4490 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBDAAAgQUIUQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4497 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBDAAAgQUIUQh78CgAACw== +invalid invalid memory_copy.wast:4504 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBDAAAgQUIURAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4511 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBDAAAgQUQAAAAAAAA0QEEe/AoAAAs= +invalid invalid memory_copy.wast:4518 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBDAAAgQUQAAAAAAAA0QEMAAPBB/AoAAAs= +invalid invalid memory_copy.wast:4525 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBDAAAgQUQAAAAAAAA0QEIe/AoAAAs= +invalid invalid memory_copy.wast:4532 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACh8BHQBDAAAgQUQAAAAAAAA0QEQAAAAAAAA+QPwKAAAL +invalid invalid memory_copy.wast:4539 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg4BDABCCkEUQR78CgAACw== +invalid invalid memory_copy.wast:4546 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBCCkEUQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4553 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg4BDABCCkEUQh78CgAACw== +invalid invalid memory_copy.wast:4560 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBCCkEURAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4567 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBCCkMAAKBBQR78CgAACw== +invalid invalid memory_copy.wast:4574 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBCCkMAAKBBQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4581 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBCCkMAAKBBQh78CgAACw== +invalid invalid memory_copy.wast:4588 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBCCkMAAKBBRAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4595 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg4BDABCCkIUQR78CgAACw== +invalid invalid memory_copy.wast:4602 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChEBDwBCCkIUQwAA8EH8CgAACw== +invalid invalid memory_copy.wast:4609 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg4BDABCCkIUQh78CgAACw== +invalid invalid memory_copy.wast:4616 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBCCkIURAAAAAAAAD5A/AoAAAs= +invalid invalid memory_copy.wast:4623 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBCCkQAAAAAAAA0QEEe/AoAAAs= +invalid invalid memory_copy.wast:4630 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBCCkQAAAAAAAA0QEMAAPBB/AoAAAs= +invalid invalid memory_copy.wast:4637 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBCCkQAAAAAAAA0QEIe/AoAAAs= +invalid invalid memory_copy.wast:4644 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChwBGgBCCkQAAAAAAAA0QEQAAAAAAAA+QPwKAAAL +invalid invalid memory_copy.wast:4651 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBEAAAAAAAAJEBBFEEe/AoAAAs= +invalid invalid memory_copy.wast:4658 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBEAAAAAAAAJEBBFEMAAPBB/AoAAAs= +invalid invalid memory_copy.wast:4665 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBEAAAAAAAAJEBBFEIe/AoAAAs= +invalid invalid memory_copy.wast:4672 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChwBGgBEAAAAAAAAJEBBFEQAAAAAAAA+QPwKAAAL +invalid invalid memory_copy.wast:4679 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBEAAAAAAAAJEBDAACgQUEe/AoAAAs= +invalid invalid memory_copy.wast:4686 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBEAAAAAAAAJEBDAACgQUMAAPBB/AoAAAs= +invalid invalid memory_copy.wast:4693 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBEAAAAAAAAJEBDAACgQUIe/AoAAAs= +invalid invalid memory_copy.wast:4700 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACh8BHQBEAAAAAAAAJEBDAACgQUQAAAAAAAA+QPwKAAAL +invalid invalid memory_copy.wast:4707 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBEAAAAAAAAJEBCFEEe/AoAAAs= +invalid invalid memory_copy.wast:4714 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChgBFgBEAAAAAAAAJEBCFEMAAPBB/AoAAAs= +invalid invalid memory_copy.wast:4721 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChUBEwBEAAAAAAAAJEBCFEIe/AoAAAs= +invalid invalid memory_copy.wast:4728 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChwBGgBEAAAAAAAAJEBCFEQAAAAAAAA+QPwKAAAL +invalid invalid memory_copy.wast:4735 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChwBGgBEAAAAAAAAJEBEAAAAAAAANEBBHvwKAAAL +invalid invalid memory_copy.wast:4742 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACh8BHQBEAAAAAAAAJEBEAAAAAAAANEBDAADwQfwKAAAL +invalid invalid memory_copy.wast:4749 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChwBGgBEAAAAAAAAJEBEAAAAAAAANEBCHvwKAAAL +invalid invalid memory_copy.wast:4756 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACiMBIQBEAAAAAAAAJEBEAAAAAAAANEBEAAAAAAAAPkD8CgAACw== +valid valid memory_copy.wast:4763 AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpAAhYAQQpB1QBBCvwLAEEJQQpBBfwKAAALJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCw== +valid valid memory_copy.wast:4789 AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpAAhYAQQpB1QBBCvwLAEEQQQ9BBfwKAAALJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCw== +valid valid memory_copy.wast:4815 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoTAREAQYD+A0GAgAJBgQL8CgAACw== +valid valid memory_copy.wast:4821 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYB+QYCAAUGBAvwKAAAL +valid valid memory_copy.wast:4827 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoTAREAQYCAAkGA/gNBgQL8CgAACw== +valid valid memory_copy.wast:4833 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCAAUGAfkGBAvwKAAAL +valid valid memory_copy.wast:4839 AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpUAioAQQBB1QBBgIAC/AsAQYCAAkGqAUGAgAL8CwBBgKACQYDgAUEA/AoAAAsnAANAIAAgAUYEQEF/DwsgAC0AACACRgRAIABBAWohAAwBCwsgAA8L +valid valid memory_copy.wast:4863 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCABEGA4AFBAPwKAAAL +valid valid memory_copy.wast:4869 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCACEGA4AFBAPwKAAAL +valid valid memory_copy.wast:4875 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCgAkGAgARBAPwKAAAL +valid valid memory_copy.wast:4881 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCgAkGAgAhBAPwKAAAL +valid valid memory_copy.wast:4887 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCABEGAgARBAPwKAAAL +valid valid memory_copy.wast:4893 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCACEGAgAhBAPwKAAAL +valid valid memory_copy.wast:4899 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= +valid valid memory_fill.wast:6 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo5AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsPAEGA/gNB1QBBgAL8CwAL +valid valid memory_fill.wast:28 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo5AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsPAEGA/gNB1QBBgQL8CwAL +valid valid memory_fill.wast:46 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAfkHVAEGBAvwLAAs= +valid valid memory_fill.wast:64 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo2AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsMAEESQdUAQQD8CwAL +valid valid memory_fill.wast:84 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAgARB1QBBAPwLAAs= +valid valid memory_fill.wast:102 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAgAhB1QBBAPwLAAs= +valid valid memory_fill.wast:120 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEEBQaoBQf7/A/wLAAs= +valid valid memory_fill.wast:145 AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQpAAicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsWAEESQdUAQQr8CwBBFUGqAUEE/AsACw== +invalid invalid memory_fill.wast:175 AGFzbQEAAAABBAFgAAADAgEABwoBBnRlc3RmbgAACg0BCwBBCkEUQR78CwAL +invalid invalid memory_fill.wast:181 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBBCkEUQwAA8EH8CwAL +invalid invalid memory_fill.wast:188 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg0BCwBBCkEUQh78CwAL +invalid invalid memory_fill.wast:195 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBBCkEURAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:202 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBBCkMAAKBBQR78CwAL +invalid invalid memory_fill.wast:209 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChMBEQBBCkMAAKBBQwAA8EH8CwAL +invalid invalid memory_fill.wast:216 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBBCkMAAKBBQh78CwAL +invalid invalid memory_fill.wast:223 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBBCkMAAKBBRAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:230 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg0BCwBBCkIUQR78CwAL +invalid invalid memory_fill.wast:237 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBBCkIUQwAA8EH8CwAL +invalid invalid memory_fill.wast:244 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg0BCwBBCkIUQh78CwAL +invalid invalid memory_fill.wast:251 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBBCkIURAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:258 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBBCkQAAAAAAAA0QEEe/AsACw== +invalid invalid memory_fill.wast:265 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBBCkQAAAAAAAA0QEMAAPBB/AsACw== +invalid invalid memory_fill.wast:272 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBBCkQAAAAAAAA0QEIe/AsACw== +invalid invalid memory_fill.wast:279 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBBCkQAAAAAAAA0QEQAAAAAAAA+QPwLAAs= +invalid invalid memory_fill.wast:286 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBDAAAgQUEUQR78CwAL +invalid invalid memory_fill.wast:293 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChMBEQBDAAAgQUEUQwAA8EH8CwAL +invalid invalid memory_fill.wast:300 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBDAAAgQUEUQh78CwAL +invalid invalid memory_fill.wast:307 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBDAAAgQUEURAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:314 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChMBEQBDAAAgQUMAAKBBQR78CwAL +invalid invalid memory_fill.wast:321 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChYBFABDAAAgQUMAAKBBQwAA8EH8CwAL +invalid invalid memory_fill.wast:328 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChMBEQBDAAAgQUMAAKBBQh78CwAL +invalid invalid memory_fill.wast:335 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChoBGABDAAAgQUMAAKBBRAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:342 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBDAAAgQUIUQR78CwAL +invalid invalid memory_fill.wast:349 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChMBEQBDAAAgQUIUQwAA8EH8CwAL +invalid invalid memory_fill.wast:356 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBDAAAgQUIUQh78CwAL +invalid invalid memory_fill.wast:363 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBDAAAgQUIURAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:370 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBDAAAgQUQAAAAAAAA0QEEe/AsACw== +invalid invalid memory_fill.wast:377 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChoBGABDAAAgQUQAAAAAAAA0QEMAAPBB/AsACw== +invalid invalid memory_fill.wast:384 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBDAAAgQUQAAAAAAAA0QEIe/AsACw== +invalid invalid memory_fill.wast:391 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACh4BHABDAAAgQUQAAAAAAAA0QEQAAAAAAAA+QPwLAAs= +invalid invalid memory_fill.wast:398 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg0BCwBCCkEUQR78CwAL +invalid invalid memory_fill.wast:405 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBCCkEUQwAA8EH8CwAL +invalid invalid memory_fill.wast:412 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg0BCwBCCkEUQh78CwAL +invalid invalid memory_fill.wast:419 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBCCkEURAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:426 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBCCkMAAKBBQR78CwAL +invalid invalid memory_fill.wast:433 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChMBEQBCCkMAAKBBQwAA8EH8CwAL +invalid invalid memory_fill.wast:440 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBCCkMAAKBBQh78CwAL +invalid invalid memory_fill.wast:447 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBCCkMAAKBBRAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:454 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg0BCwBCCkIUQR78CwAL +invalid invalid memory_fill.wast:461 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChABDgBCCkIUQwAA8EH8CwAL +invalid invalid memory_fill.wast:468 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACg0BCwBCCkIUQh78CwAL +invalid invalid memory_fill.wast:475 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBCCkIURAAAAAAAAD5A/AsACw== +invalid invalid memory_fill.wast:482 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBCCkQAAAAAAAA0QEEe/AsACw== +invalid invalid memory_fill.wast:489 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBCCkQAAAAAAAA0QEMAAPBB/AsACw== +invalid invalid memory_fill.wast:496 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBCCkQAAAAAAAA0QEIe/AsACw== +invalid invalid memory_fill.wast:503 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBCCkQAAAAAAAA0QEQAAAAAAAA+QPwLAAs= +invalid invalid memory_fill.wast:510 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBEAAAAAAAAJEBBFEEe/AsACw== +invalid invalid memory_fill.wast:517 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBEAAAAAAAAJEBBFEMAAPBB/AsACw== +invalid invalid memory_fill.wast:524 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBEAAAAAAAAJEBBFEIe/AsACw== +invalid invalid memory_fill.wast:531 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBEAAAAAAAAJEBBFEQAAAAAAAA+QPwLAAs= +invalid invalid memory_fill.wast:538 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBEAAAAAAAAJEBDAACgQUEe/AsACw== +invalid invalid memory_fill.wast:545 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChoBGABEAAAAAAAAJEBDAACgQUMAAPBB/AsACw== +invalid invalid memory_fill.wast:552 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBEAAAAAAAAJEBDAACgQUIe/AsACw== +invalid invalid memory_fill.wast:559 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACh4BHABEAAAAAAAAJEBDAACgQUQAAAAAAAA+QPwLAAs= +invalid invalid memory_fill.wast:566 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBEAAAAAAAAJEBCFEEe/AsACw== +invalid invalid memory_fill.wast:573 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChcBFQBEAAAAAAAAJEBCFEMAAPBB/AsACw== +invalid invalid memory_fill.wast:580 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChQBEgBEAAAAAAAAJEBCFEIe/AsACw== +invalid invalid memory_fill.wast:587 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBEAAAAAAAAJEBCFEQAAAAAAAA+QPwLAAs= +invalid invalid memory_fill.wast:594 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBEAAAAAAAAJEBEAAAAAAAANEBBHvwLAAs= +invalid invalid memory_fill.wast:601 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACh4BHABEAAAAAAAAJEBEAAAAAAAANEBDAADwQfwLAAs= +invalid invalid memory_fill.wast:608 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAAChsBGQBEAAAAAAAAJEBEAAAAAAAANEBCHvwLAAs= +invalid invalid memory_fill.wast:615 AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwoBBnRlc3RmbgAACiIBIABEAAAAAAAAJEBEAAAAAAAANEBEAAAAAAAAPkD8CwAL +valid valid memory_fill.wast:621 AGFzbQEAAAABDgJgA39/fwF/YAN/f38AAwMCAAEFBAEBAQEHFAIKY2hlY2tSYW5nZQAAA3J1bgABCjUCJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCwsAIAAgASAC/AsACw== +valid valid memory_grow.wast:1 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8DBwYAAQABAgAFAwEAAAdXBgxsb2FkX2F0X3plcm8AAA1zdG9yZV9hdF96ZXJvAAERbG9hZF9hdF9wYWdlX3NpemUAAhJzdG9yZV9hdF9wYWdlX3NpemUAAwRncm93AAQEc2l6ZQAFCjUGBwBBACgCAAsJAEEAQQI2AgALCQBBgIAEKAIACwsAQYCABEEDNgIACwYAIABAAAsEAD8ACw== +valid valid memory_grow.wast:36 AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAAcIAQRncm93AAAKCAEGACAAQAAL +valid valid memory_grow.wast:50 AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAAoHCAEEZ3JvdwAACggBBgAgAEAACw== +valid valid memory_grow.wast:66 AGFzbQEAAAABDAJgAX8Bf2ACf38BfwMDAgABBQMBAAEHHAIEZ3JvdwAAEWNoZWNrLW1lbW9yeS16ZXJvAAEKPAIGACAAQAALMwEBf0EBIQICQANAIAAtAAAhAiACQQBHDQEgACABTw0BIABBAWohACAAIAFNDQALCyACCw== +valid valid memory_grow.wast:101 AGFzbQEAAAABFQRgA39/fwF/YAABf2AAAGACf38BfwMnJgECAQECAQEBAQEBAwMBAAEBAQEBAQECAQIBAQICAgIBAQEBAQEBBAUBcAEBAQUDAQABBgYBfwFBAAsHtwUlC2FzLWJyLXZhbHVlAAANYXMtYnJfaWYtY29uZAABDmFzLWJyX2lmLXZhbHVlAAITYXMtYnJfaWYtdmFsdWUtY29uZAADEWFzLWJyX3RhYmxlLWluZGV4AAQRYXMtYnJfdGFibGUtdmFsdWUABRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAGD2FzLXJldHVybi12YWx1ZQAHCmFzLWlmLWNvbmQACAphcy1pZi10aGVuAAkKYXMtaWYtZWxzZQAKD2FzLXNlbGVjdC1maXJzdAALEGFzLXNlbGVjdC1zZWNvbmQADA5hcy1zZWxlY3QtY29uZAANDWFzLWNhbGwtZmlyc3QADwthcy1jYWxsLW1pZAAQDGFzLWNhbGwtbGFzdAARFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAEhRhcy1jYWxsX2luZGlyZWN0LW1pZAATFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAUFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAFRJhcy1sb2NhbC5zZXQtdmFsdWUAFhJhcy1sb2NhbC50ZWUtdmFsdWUAFxNhcy1nbG9iYWwuc2V0LXZhbHVlABgPYXMtbG9hZC1hZGRyZXNzABkQYXMtbG9hZE4tYWRkcmVzcwAaEGFzLXN0b3JlLWFkZHJlc3MAGw5hcy1zdG9yZS12YWx1ZQAcEWFzLXN0b3JlTi1hZGRyZXNzAB0PYXMtc3RvcmVOLXZhbHVlAB4QYXMtdW5hcnktb3BlcmFuZAAfDmFzLWJpbmFyeS1sZWZ0ACAPYXMtYmluYXJ5LXJpZ2h0ACEPYXMtdGVzdC1vcGVyYW5kACIPYXMtY29tcGFyZS1sZWZ0ACMQYXMtY29tcGFyZS1yaWdodAAkE2FzLW1lbW9yeS5ncm93LXNpemUAJQkHAQBBAAsBDgraAyYLAAJ/QQBAAAwACwsLAAJAQQBAAA0ACwsQAAJ/QQBAAEEBDQAaQQcLCxAAAn9BBkEAQAANABpBBwsLDgACQEEAQAAOAgAAAAsLEgACf0EAQABBAQ4CAAAAQQcLCxEAAn9BBkEAQAAOAQAAQQcLCwcAQQBAAA8LDgBBAEAABH9BAAVBAQsLDgBBAQR/QQBAAAVBAAsLDgBBAAR/QQAFQQBAAAsLCwBBAEAAIAAgARsLCwAgAEEAQAAgARsLCwBBAEEBQQBAABsLBABBfwsMAEEAQABBAkEDEA4LDABBAUEAQABBAxAOCwwAQQFBAkEAQAAQDgsPAEEAQABBAkEDQQARAAALDwBBAUEAQABBA0EAEQAACw8AQQFBAkEAQABBABEAAAsPAEEBQQJBA0EAQAARAAALCgEBf0EAQAAhAAsKAQF/QQBAACIACwoBAX9BAEAAJAALCQBBAEAAKAIACwkAQQBAACwAAAsLAEEAQABBBzYCAAsLAEECQQBAADYCAAsLAEEAQABBBzoAAAsLAEECQQBAADsBAAsHAEEAQABnCwkAQQBAAEEKagsJAEEKQQBAAGsLBwBBAEAARQsJAEEAQABBCkwLCQBBCkEAQABHCwgAQQBAAEAACw== +valid valid memory_grow.wast:312 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABBxECBm1lbW9yeQIABGdyb3cAAAoIAQYAQQFAAAs= +valid valid memory_grow.wast:318 AGFzbQEAAAABBQFgAAF/AhgBDGdyb3duLW1lbW9yeQZtZW1vcnkCAAIDAgEABxECBm1lbW9yeQIABGdyb3cAAAoIAQYAQQFAAAs= +valid valid memory_grow.wast:325 AGFzbQEAAAABBQFgAAF/AiEBFWdyb3duLWltcG9ydGVkLW1lbW9yeQZtZW1vcnkCAAMDAgEABwgBBHNpemUAAAoGAQQAPwAL +invalid invalid memory_grow.wast:334 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQAACgYBBABAAAs= +invalid invalid memory_grow.wast:343 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQAACgsBCQBBAAJ/QAALCw== +invalid invalid memory_grow.wast:353 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQAACgsBCQBBAAN/QAALCw== +invalid invalid memory_grow.wast:363 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQAACg0BCwBBAEEABH9AAAsL +invalid invalid memory_grow.wast:374 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABCgsBCQBDAAAAAEAACw== +invalid invalid memory_grow.wast:384 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCAEGAEEAQAAL +invalid invalid memory_grow.wast:393 AGFzbQEAAAABBQFgAAF9AwIBAAUDAQABCggBBgBBAEAACw== +valid valid memory_init.wast:6 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABCg0CAwABCwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY= +valid valid memory_init.wast:50 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEChYCDABBB0EAQQT8CAEACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY= +valid valid memory_init.wast:94 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEChYCDABBD0EBQQP8CAMACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY= +valid valid memory_init.wast:138 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEClgCTgBBB0EAQQT8CAEA/AkBQQ9BAUED/AgDAPwJA0EUQQ9BBfwKAABBFUEdQQH8CgAAQRhBCkEB/AoAAEENQQtBBPwKAABBE0EUQQX8CgAACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY= +invalid invalid memory_init.wast:190 AGFzbQEAAAABBAFgAAADAgEABwgBBHRlc3QAAAoHAQUA/AkACw== +invalid invalid memory_init.wast:196 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCgcBBQD8CQQLCwQBAQE3 +valid valid memory_init.wast:203 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCgoBCAD8CQD8CQALCwQBAQE3 +valid valid memory_init.wast:211 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChIBEAD8CQBB0glBAUEB/AgAAAsLBAEBATc= +valid valid memory_init.wast:219 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAUEB/AgAAAsLBwEAQQALATc= +invalid invalid memory_init.wast:227 AGFzbQEAAAABBAFgAAADAgEABwgBBHRlc3QAAAoPAQ0AQdIJQQFBAfwIAQAL +invalid invalid memory_init.wast:233 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAUEB/AgBAAsLBAEBATc= +valid valid memory_init.wast:240 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBBAUEAQQH8CAAAQQFBAEEB/AgAAAsLBAEBATc= +valid valid memory_init.wast:248 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAEEF/AgAAAsLBAEBATc= +valid valid memory_init.wast:255 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAkED/AgAAAsLBAEBATc= +valid valid memory_init.wast:262 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBB/v8DQQFBA/wIAAALCwQBAQE3 +valid valid memory_init.wast:269 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBBEEA/AgAAAsLBAEBATc= +valid valid memory_init.wast:276 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAUEA/AgAAAsLBAEBATc= +valid valid memory_init.wast:283 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgYAEQQBBAPwIAAALCwQBAQE3 +valid valid memory_init.wast:290 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgIAEQQBBAPwIAAALCwQBAQE3 +valid valid memory_init.wast:297 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgIAEQQFBAPwIAAALCwQBAQE3 +valid valid memory_init.wast:304 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgYAEQQRBAPwIAAALCwQBAQE3 +invalid invalid memory_init.wast:312 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBBAUEBQwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:320 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg4BDABBAUEBQgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:328 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBBAUEBRAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:336 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBBAUMAAIA/QQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:344 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChQBEgBBAUMAAIA/QwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:352 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBBAUMAAIA/QgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:360 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBBAUMAAIA/RAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:368 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg4BDABBAUIBQQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:376 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBBAUIBQwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:384 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg4BDABBAUIBQgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:392 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBBAUIBRAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:400 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBBAUQAAAAAAADwP0EB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:408 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBBAUQAAAAAAADwP0MAAIA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:416 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBBAUQAAAAAAADwP0IB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:424 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChwBGgBBAUQAAAAAAADwP0QAAAAAAADwP/wIAAALCwQBAQE3 +invalid invalid memory_init.wast:432 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBDAACAP0EBQQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:440 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChQBEgBDAACAP0EBQwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:448 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBDAACAP0EBQgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:456 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBDAACAP0EBRAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:464 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChQBEgBDAACAP0MAAIA/QQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:472 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChcBFQBDAACAP0MAAIA/QwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:480 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChQBEgBDAACAP0MAAIA/QgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:488 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChsBGQBDAACAP0MAAIA/RAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:496 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBDAACAP0IBQQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:504 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChQBEgBDAACAP0IBQwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:512 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBDAACAP0IBQgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:520 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBDAACAP0IBRAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:528 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBDAACAP0QAAAAAAADwP0EB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:536 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChsBGQBDAACAP0QAAAAAAADwP0MAAIA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:544 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBDAACAP0QAAAAAAADwP0IB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:552 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCh8BHQBDAACAP0QAAAAAAADwP0QAAAAAAADwP/wIAAALCwQBAQE3 +invalid invalid memory_init.wast:560 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg4BDABCAUEBQQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:568 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBCAUEBQwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:576 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg4BDABCAUEBQgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:584 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBCAUEBRAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:592 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBCAUMAAIA/QQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:600 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChQBEgBCAUMAAIA/QwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:608 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBCAUMAAIA/QgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:616 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBCAUMAAIA/RAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:624 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg4BDABCAUIBQQH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:632 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChEBDwBCAUIBQwAAgD/8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:640 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg4BDABCAUIBQgH8CAAACwsEAQEBNw== +invalid invalid memory_init.wast:648 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBCAUIBRAAAAAAAAPA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:656 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBCAUQAAAAAAADwP0EB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:664 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBCAUQAAAAAAADwP0MAAIA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:672 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBCAUQAAAAAAADwP0IB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:680 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChwBGgBCAUQAAAAAAADwP0QAAAAAAADwP/wIAAALCwQBAQE3 +invalid invalid memory_init.wast:688 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBEAAAAAAAA8D9BAUEB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:696 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBEAAAAAAAA8D9BAUMAAIA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:704 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBEAAAAAAAA8D9BAUIB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:712 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChwBGgBEAAAAAAAA8D9BAUQAAAAAAADwP/wIAAALCwQBAQE3 +invalid invalid memory_init.wast:720 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBEAAAAAAAA8D9DAACAP0EB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:728 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChsBGQBEAAAAAAAA8D9DAACAP0MAAIA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:736 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBEAAAAAAAA8D9DAACAP0IB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:744 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCh8BHQBEAAAAAAAA8D9DAACAP0QAAAAAAADwP/wIAAALCwQBAQE3 +invalid invalid memory_init.wast:752 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBEAAAAAAAA8D9CAUEB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:760 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBEAAAAAAAA8D9CAUMAAIA//AgAAAsLBAEBATc= +invalid invalid memory_init.wast:768 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChUBEwBEAAAAAAAA8D9CAUIB/AgAAAsLBAEBATc= +invalid invalid memory_init.wast:776 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChwBGgBEAAAAAAAA8D9CAUQAAAAAAADwP/wIAAALCwQBAQE3 +invalid invalid memory_init.wast:784 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChwBGgBEAAAAAAAA8D9EAAAAAAAA8D9BAfwIAAALCwQBAQE3 +invalid invalid memory_init.wast:792 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCh8BHQBEAAAAAAAA8D9EAAAAAAAA8D9DAACAP/wIAAALCwQBAQE3 +invalid invalid memory_init.wast:800 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChwBGgBEAAAAAAAA8D9EAAAAAAAA8D9CAfwIAAALCwQBAQE3 +invalid invalid memory_init.wast:808 AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCiMBIQBEAAAAAAAA8D9EAAAAAAAA8D9EAAAAAAAA8D/8CAAACwsEAQEBNw== +valid valid memory_init.wast:815 AGFzbQEAAAABDQJgA39/fwF/YAJ/fwADAwIAAQUEAQEBAQcUAgpjaGVja1JhbmdlAAADcnVuAAEMAQEKNgInAANAIAAgAUYEQEF/DwsgAC0AACACRgRAIABBAWohAAwBCwsgAA8LDAAgAEEAIAH8CAAACwsTAQEQQkJCQkJCQkJCQkJCQkJCQg== +valid valid memory_init.wast:907 AGFzbQEAAAABDQJgA39/fwF/YAJ/fwADAwIAAQUDAQABBxQCCmNoZWNrUmFuZ2UAAANydW4AAQwBAQo2AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsMACAAQQAgAfwIAAALCxMBARBCQkJCQkJCQkJCQkJCQkJC +valid valid memory_init.wast:954 AGFzbQEAAAABBAFgAAADAgEABQMBAAEMAUEKDgEMAEEAQQBBAPwIQAALC4MBQQEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA= +valid valid memory_redundancy.wast:5 AGFzbQEAAAABEQRgAABgAAF/YAABfWABfwF/AwcGAAEBAgMBBQQBAQEBB2sGD3plcm9fZXZlcnl0aGluZwAAEnRlc3Rfc3RvcmVfdG9fbG9hZAABE3Rlc3RfcmVkdW5kYW50X2xvYWQAAg90ZXN0X2RlYWRfc3RvcmUAAwZtYWxsb2MABA9tYWxsb2NfYWxpYXNpbmcABQqlAQYeAEEAQQA2AgBBBEEANgIAQQhBADYCAEEMQQA2AgALGABBCEEANgIAQQVDAAAAgDgCAEEIKAIACyIBAn9BCCgCACEAQQVBgICAgHg2AgBBCCgCACEBIAAgAWoLHwEBfUEIQaPGjJkCNgIAQQsqAgAhAEEIQQA2AgAgAAsEAEEQCyMBAn9BBBAEIQBBBBAEIQEgAEEqNgIAIAFBKzYCACAAKAIACw== +valid valid memory_size.wast:1 AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQMBAAAHDwIEc2l6ZQAABGdyb3cAAQoOAgQAPwALBwAgAEAAGgs= +valid valid memory_size.wast:15 AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQMBAAEHDwIEc2l6ZQAABGdyb3cAAQoOAgQAPwALBwAgAEAAGgs= +valid valid memory_size.wast:29 AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQQBAQACBw8CBHNpemUAAARncm93AAEKDgIEAD8ACwcAIABAABoL +valid valid memory_size.wast:47 AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQQBAQMIBw8CBHNpemUAAARncm93AAEKDgIEAD8ACwcAIABAABoL +invalid invalid memory_size.wast:69 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKBgEEAD8ACw== +invalid invalid memory_size.wast:78 AGFzbQEAAAABBQFgAAF9AwIBAAUDAQABCgYBBAA/AAs= +valid valid memory_trap.wast:1 AGFzbQEAAAABDwNgAAF/YAJ/fwBgAX8BfwMFBAABAgIFAwEAAQceAwVzdG9yZQABBGxvYWQAAgttZW1vcnkuZ3JvdwADCioECQA/AEGAgARsCwwAEAAgAGogATYCAAsKABAAIABqKAIACwYAIABAAAs= +valid valid memory_trap.wast:35 AGFzbQEAAAABKQhgAX8Bf2ABfwF+YAF/AX1gAX8BfGACf38AYAJ/fgBgAn99AGACf3wAAxgXAAECAwAAAAABAQEBAQEEBQYHBAQFBQUFAwEAAQezAhcIaTMyLmxvYWQAAAhpNjQubG9hZAABCGYzMi5sb2FkAAIIZjY0LmxvYWQAAwtpMzIubG9hZDhfcwAEC2kzMi5sb2FkOF91AAUMaTMyLmxvYWQxNl9zAAYMaTMyLmxvYWQxNl91AAcLaTY0LmxvYWQ4X3MACAtpNjQubG9hZDhfdQAJDGk2NC5sb2FkMTZfcwAKDGk2NC5sb2FkMTZfdQALDGk2NC5sb2FkMzJfcwAMDGk2NC5sb2FkMzJfdQANCWkzMi5zdG9yZQAOCWk2NC5zdG9yZQAPCWYzMi5zdG9yZQAQCWY2NC5zdG9yZQARCmkzMi5zdG9yZTgAEgtpMzIuc3RvcmUxNgATCmk2NC5zdG9yZTgAFAtpNjQuc3RvcmUxNgAVC2k2NC5zdG9yZTMyABYKywEXBwAgACgCAAsHACAAKQMACwcAIAAqAgALBwAgACsDAAsHACAALAAACwcAIAAtAAALBwAgAC4BAAsHACAALwEACwcAIAAwAAALBwAgADEAAAsHACAAMgEACwcAIAAzAQALBwAgADQCAAsHACAANQIACwkAIAAgATYCAAsJACAAIAE3AwALCQAgACABOAIACwkAIAAgATkDAAsJACAAIAE6AAALCQAgACABOwEACwkAIAAgATwAAAsJACAAIAE9AQALCQAgACABPgIACwsdAgBBAAsIYWJjZGVmZ2gAQfj/AwsIYWJjZGVmZ2g= +valid valid names.wast:4 AGFzbQEAAAABBQFgAAF/AwIBAAcHAQNmb28AAAoGAQQAQQAL +valid valid names.wast:12 AGFzbQEAAAABBQFgAAF/AwIBAAcHAQNmb28AAAoGAQQAQQEL +valid valid names.wast:19 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 +valid valid names.wast:1095 AGFzbQEAAAABCgJgAX8AYAJ/fwACKwIIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTMyAAADAgEBBwsBB3ByaW50MzIAAgoMAQoAIAAQACABEAEL +valid valid nop.wast:3 AGFzbQEAAAABIwdgAn9/AX9gAABgA39/fwF/YAABf2ABfwBgAX8Bf2ACf38AA1RTAQIDAwMDBAQEBQUFBQUDAwMDAwMDAwQEBAUFBQUFBQUFBQUFBQUFAgICAgIFBQUFBQUFBQUFBQUFBQUFBQADAwMDAwUFBQUFBQMDAwUFBQYGBgYEBQFwAQEBBQMBAAEGBgF/AUEACwfqC1ANYXMtZnVuYy1maXJzdAACC2FzLWZ1bmMtbWlkAAMMYXMtZnVuYy1sYXN0AAQSYXMtZnVuYy1ldmVyeXdoZXJlAAUNYXMtZHJvcC1maXJzdAAGDGFzLWRyb3AtbGFzdAAHEmFzLWRyb3AtZXZlcnl3aGVyZQAID2FzLXNlbGVjdC1maXJzdAAJDmFzLXNlbGVjdC1taWQxAAoOYXMtc2VsZWN0LW1pZDIACw5hcy1zZWxlY3QtbGFzdAAMFGFzLXNlbGVjdC1ldmVyeXdoZXJlAA0OYXMtYmxvY2stZmlyc3QADgxhcy1ibG9jay1taWQADw1hcy1ibG9jay1sYXN0ABATYXMtYmxvY2stZXZlcnl3aGVyZQARDWFzLWxvb3AtZmlyc3QAEgthcy1sb29wLW1pZAATDGFzLWxvb3AtbGFzdAAUEmFzLWxvb3AtZXZlcnl3aGVyZQAVD2FzLWlmLWNvbmRpdGlvbgAWCmFzLWlmLXRoZW4AFwphcy1pZi1lbHNlABgLYXMtYnItZmlyc3QAGQphcy1ici1sYXN0ABoQYXMtYnItZXZlcnl3aGVyZQAbDmFzLWJyX2lmLWZpcnN0ABwMYXMtYnJfaWYtbWlkAB0NYXMtYnJfaWYtbGFzdAAeE2FzLWJyX2lmLWV2ZXJ5d2hlcmUAHxFhcy1icl90YWJsZS1maXJzdAAgD2FzLWJyX3RhYmxlLW1pZAAhEGFzLWJyX3RhYmxlLWxhc3QAIhZhcy1icl90YWJsZS1ldmVyeXdoZXJlACMPYXMtcmV0dXJuLWZpcnN0ACQOYXMtcmV0dXJuLWxhc3QAJRRhcy1yZXR1cm4tZXZlcnl3aGVyZQAmDWFzLWNhbGwtZmlyc3QAJwxhcy1jYWxsLW1pZDEAKAxhcy1jYWxsLW1pZDIAKQxhcy1jYWxsLWxhc3QAKhJhcy1jYWxsLWV2ZXJ5d2hlcmUAKw5hcy11bmFyeS1maXJzdAAsDWFzLXVuYXJ5LWxhc3QALRNhcy11bmFyeS1ldmVyeXdoZXJlAC4PYXMtYmluYXJ5LWZpcnN0AC8NYXMtYmluYXJ5LW1pZAAwDmFzLWJpbmFyeS1sYXN0ADEUYXMtYmluYXJ5LWV2ZXJ5d2hlcmUAMg1hcy10ZXN0LWZpcnN0ADMMYXMtdGVzdC1sYXN0ADQSYXMtdGVzdC1ldmVyeXdoZXJlADUQYXMtY29tcGFyZS1maXJzdAA2DmFzLWNvbXBhcmUtbWlkADcPYXMtY29tcGFyZS1sYXN0ADgVYXMtY29tcGFyZS1ldmVyeXdoZXJlADkUYXMtbWVtb3J5Lmdyb3ctZmlyc3QAOhNhcy1tZW1vcnkuZ3Jvdy1sYXN0ADsZYXMtbWVtb3J5Lmdyb3ctZXZlcnl3aGVyZQA8FmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAPhVhcy1jYWxsX2luZGlyZWN0LW1pZDEAPxVhcy1jYWxsX2luZGlyZWN0LW1pZDIAQBVhcy1jYWxsX2luZGlyZWN0LWxhc3QAQRthcy1jYWxsX2luZGlyZWN0LWV2ZXJ5d2hlcmUAQhJhcy1sb2NhbC5zZXQtZmlyc3QAQxFhcy1sb2NhbC5zZXQtbGFzdABEF2FzLWxvY2FsLnNldC1ldmVyeXdoZXJlAEUSYXMtbG9jYWwudGVlLWZpcnN0AEYRYXMtbG9jYWwudGVlLWxhc3QARxdhcy1sb2NhbC50ZWUtZXZlcnl3aGVyZQBIE2FzLWdsb2JhbC5zZXQtZmlyc3QASRJhcy1nbG9iYWwuc2V0LWxhc3QAShhhcy1nbG9iYWwuc2V0LWV2ZXJ5d2hlcmUASw1hcy1sb2FkLWZpcnN0AEwMYXMtbG9hZC1sYXN0AE0SYXMtbG9hZC1ldmVyeXdoZXJlAE4OYXMtc3RvcmUtZmlyc3QATwxhcy1zdG9yZS1taWQAUA1hcy1zdG9yZS1sYXN0AFETYXMtc3RvcmUtZXZlcnl3aGVyZQBSCQcBAEEACwE9CqIHUwIACwoAIAAgASACa2oLBQABQQELBwAQAAFBAgsHABAAQQMBCwsAAQEQAAFBBAEBCwYAASAAGgsGACAAARoLCQABASAAAQEaCwoAASAAIAAgABsLCgAgAAEgACAAGwsKACAAIAABIAAbCwoAIAAgACAAARsLEAABIAABASAAAQEgAAEBGwsIAAJ/AUECCwsKAAJ/EAABQQILCwsAAn8BEABBAwELCw4AAn8BARAAAUEEAQELCwgAA38BQQILCwoAA38QAAFBAgsLCgADfxAAQQMBCwsOAAN/AQEQAAFBBAEBCwsKACAAAQRAEAALCwsAIAAEQAEFEAALCwsAIAAEQBAABQELCwoAAn8BIAAMAAsLCgACfyAAAQwACwsNAAJ/AQEgAAEBDAALCwwAAn8BIAAgAA0ACwsMAAJ/IAABIAANAAsLDAACfyAAIAABDQALCxEAAn8BASAAAQEgAAEBDQALCw4AAn8BIAAgAA4BAAALCw4AAn8gAAEgAA4BAAALCw4AAn8gACAAAQ4BAAALCxMAAn8BASAAAQEgAAEBDgEAAAsLBgABIAAPCwYAIAABDwsJAAEBIAABAQ8LCwABIAAgASACEAELCwAgAAEgASACEAELCwAgACABASACEAELCwAgACABIAIBEAELEgABASAAAQEgAQEBIAIBARABCwYAASAAaAsGACAAAWgLCQABASAAAQFoCwgAASAAIABqCwgAIAABIABqCwgAIAAgAAFqCwwAASAAAQEgAAEBagsGAAEgAEULBgAgAAFFCwkAAQEgAAEBRQsIAAEgACAARwsIACAAASAARwsIACAAIAABSQsMAAEgAAEBIAABAUwLBwABIABAAAsHACAAAUAACwoAAQEgAAEBQAALBAAgAAsPAAJ/AUEBQQJBABEAAAsLDwACf0EBAUECQQARAAALCw8AAn9BAUECAUEAEQAACwsPAAJ/QQFBAkEAAREAAAsLFgACfwEBQQEBAUECAQFBAAEBEQAACwsJAAFBAiEAIAALCQBBAgEhACAACwwAAQFBAgEBIQAgAAsHAAFBAiIACwcAQQIBIgALCgABAUECAQEiAAsJAAFBAiQAIwALCQBBAgEkACMACwwAAQFBAgEBJAAjAAsIAAEgACgCAAsIACAAASgCAAsLAAEBIAABASgCAAsKAAEgACABNgIACwoAIAABIAE2AgALCgAgACABATYCAAsPAAEBIAABASABAQE2AgAL +invalid invalid nop.wast:416 AGFzbQEAAAABBQFgAAF+AwIBAAoFAQMAAQs= +invalid invalid nop.wast:420 AGFzbQEAAAABBQFgAAF9AwIBAAoFAQMAAQs= +invalid invalid nop.wast:424 AGFzbQEAAAABBQFgAAF8AwIBAAoFAQMAAQs= +valid valid ref_func.wast:1 AGFzbQEAAAABBgFgAX8BfwMCAQAHBQEBZgAACgYBBAAgAAs= +valid valid ref_func.wast:6 AGFzbQEAAAABDQNgAX8Bf2AAAGAAAX8CBwEBTQFmAAADDw4AAQEBAQECAgIBAQAAAAQEAXAAAQYaBXAA0gALcADSAQtwAdIAC3AA0gMLcADSBAsHUAgJaXNfbnVsbC1mAAcJaXNfbnVsbC1nAAgJaXNfbnVsbC12AAkFc2V0LWYACgVzZXQtZwALBmNhbGwtZgAMBmNhbGwtZwANBmNhbGwtdgAOCRADAwACAwUDAAIEBgMAAgABCm4OBwAgAEEBagsIANIFGtIGGgsCAAsCAAsCAAsCAAsFANIA0QsFANIB0QsFACMC0QsGANIAJAILBgDSASQCCw8AQQDSACYAIABBABEAAAsPAEEA0gEmACAAQQARAAALDwBBACMCJgAgAEEAEQAACw== +invalid invalid ref_func.wast:69 AGFzbQEAAAABBgFgAX8BfwINAgFNAWYAAAFNAWcAAAYGAXAA0gcL +valid valid ref_func.wast:80 AGFzbQEAAAABBAFgAAADCAcAAAAAAAAABAQBcAABBgYBcADSAAsHBQEBZgABCRUEAEEACwECAEEACwEDAQABBAEAAQUKIwcCAAsCAAsCAAsCAAsCAAsCAAsPANIA0gHSAtID0gTSBQ8L +invalid invalid ref_func.wast:109 AGFzbQEAAAABBAFgAAADAgEACgcBBQDSABoL +invalid invalid ref_func.wast:113 AGFzbQEAAAABBAFgAAADAgEACAEACgcBBQDSABoL +valid valid ref_is_null.wast:1 AGFzbQEAAAABFwVgAXABf2ABbwF/YAAAYAFvAGABfwF/AwgHAAECAwIEBAQHAnAAAm8AAgdHBgdmdW5jcmVmAAAJZXh0ZXJucmVmAAEEaW5pdAADBmRlaW5pdAAEDGZ1bmNyZWYtZWxlbQAFDmV4dGVybnJlZi1lbGVtAAYJBwEAQQELAQIKOgcFACAA0QsFACAA0QsCAAsIAEEBIAAmAQsOAEEB0HAmAEEB0G8mAQsIACAAJQAQAAsIACAAJQEQAQs= +invalid invalid ref_is_null.wast:52 AGFzbQEAAAABBQFgAX8AAwIBAAoHAQUAIADRCw== +invalid invalid ref_is_null.wast:56 AGFzbQEAAAABBAFgAAADAgEACgUBAwDRCw== +valid valid ref_null.wast:1 AGFzbQEAAAABCQJgAAFvYAABcAMDAgABBgsCbwDQbwtwANBwCwcXAglleHRlcm5yZWYAAAdmdW5jcmVmAAEKCwIEANBvCwQA0HAL +valid valid return.wast:3 AGFzbQEAAAABIQdgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGACf38BfwM+PQEBAQEBAgMEBQEFAgIBAgEBAQICAgICAQICAwICAwIGBgYGAgACAgICAgICAgICBAMCAgICBAIDAgICAgIEBQFwAQEBBQMBAAEGBgF/AUEACwf4BzsIdHlwZS1pMzIAAQh0eXBlLWk2NAACCHR5cGUtZjMyAAMIdHlwZS1mNjQABA50eXBlLWkzMi12YWx1ZQAFDnR5cGUtaTY0LXZhbHVlAAYOdHlwZS1mMzItdmFsdWUABw50eXBlLWY2NC12YWx1ZQAIB251bGxhcnkACQV1bmFyeQAKDWFzLWZ1bmMtZmlyc3QACwthcy1mdW5jLW1pZAAMDGFzLWZ1bmMtbGFzdAANDWFzLWZ1bmMtdmFsdWUADg5hcy1ibG9jay1maXJzdAAPDGFzLWJsb2NrLW1pZAAQDWFzLWJsb2NrLWxhc3QAEQ5hcy1ibG9jay12YWx1ZQASDWFzLWxvb3AtZmlyc3QAEwthcy1sb29wLW1pZAAUDGFzLWxvb3AtbGFzdAAVC2FzLWJyLXZhbHVlABYNYXMtYnJfaWYtY29uZAAXDmFzLWJyX2lmLXZhbHVlABgTYXMtYnJfaWYtdmFsdWUtY29uZAAZEWFzLWJyX3RhYmxlLWluZGV4ABoRYXMtYnJfdGFibGUtdmFsdWUAGxdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAcD2FzLXJldHVybi12YWx1ZQAdCmFzLWlmLWNvbmQAHgphcy1pZi10aGVuAB8KYXMtaWYtZWxzZQAgD2FzLXNlbGVjdC1maXJzdAAhEGFzLXNlbGVjdC1zZWNvbmQAIg5hcy1zZWxlY3QtY29uZAAjDWFzLWNhbGwtZmlyc3QAJQthcy1jYWxsLW1pZAAmDGFzLWNhbGwtbGFzdAAnFWFzLWNhbGxfaW5kaXJlY3QtZnVuYwAoFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAKRRhcy1jYWxsX2luZGlyZWN0LW1pZAAqFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAArEmFzLWxvY2FsLnNldC12YWx1ZQAsEmFzLWxvY2FsLnRlZS12YWx1ZQAtE2FzLWdsb2JhbC5zZXQtdmFsdWUALg9hcy1sb2FkLWFkZHJlc3MALxBhcy1sb2FkTi1hZGRyZXNzADAQYXMtc3RvcmUtYWRkcmVzcwAxDmFzLXN0b3JlLXZhbHVlADIRYXMtc3RvcmVOLWFkZHJlc3MAMw9hcy1zdG9yZU4tdmFsdWUANBBhcy11bmFyeS1vcGVyYW5kADUOYXMtYmluYXJ5LWxlZnQANg9hcy1iaW5hcnktcmlnaHQANw9hcy10ZXN0LW9wZXJhbmQAOA9hcy1jb21wYXJlLWxlZnQAORBhcy1jb21wYXJlLXJpZ2h0ADoSYXMtY29udmVydC1vcGVyYW5kADsTYXMtbWVtb3J5Lmdyb3ctc2l6ZQA8CQcBAEEACwEkCqkFPQIACwUAD2gaCwUAD3oaCwUAD4waCwUAD5oaCwkAAn9BAQ9oCwsJAAJ+QgIPegsLDAACfUMAAEBAD4wLCxAAAnxEAAAAAAAAEEAPmgsLAwAPCwwARAAAAAAAAAhADwsHAEEBD0ECCwkAEABBAg9BAwsGAAEQAA8LCAABEABBAw8LCAACQA8QAAsLCgACQBAADxAACwsJAAJAARAADwsLCwACfwEQAEECDwsLCgADf0EDD0ECCwsMAAN/EABBBA9BAgsLCwADfwEQAEEFDwsLCgACf0EJDwwACwsIAAJADw0ACwsPAAJ/QQgPQQENABpBBwsLDwACf0EGQQkPDQAaQQcLCw8AAkBCCQ8OAgAAAAtCfwsRAAJ/QQoPQQEOAgAAAEEHCwsQAAJ/QQZBCw8OAQAAQQcLCwYAQgcPDwsNAEECDwR/QQAFQQELCw0AIAAEf0EDDwUgAQsLDQAgAAR/IAEFQQQPCwsKAEEFDyAAIAEbCwoAIABBBg8gARsLCgBBAEEBQQcPGwsEAEF/CwsAQQwPQQJBAxAkCwsAQQFBDQ9BAxAkCwsAQQFBAkEODxAkCw4AQRQPQQFBAkEDEQAACw4AQQBBFQ9BAkEDEQAACw4AQQBBAUEWD0EDEQAACw4AQQBBAUECQRcPEQAACwsBAX1BEQ8hAEF/CwkBAX9BAQ8iAAsHAEEBDyQACwsAQ5qZ2T8PKgIACwgAQh4PMAAACxMAQR4PRAAAAAAAABxAOQMAQX8LDABBAkEfDzcDAEF/CwwAQSAPQQc6AABBfwsMAEECQSEPPQEAQX8LCQBDmplZQA+MCwgAQQMPQQpqCwgAQgpCLQ99CwYAQSwPRQsPAEErD0QAAAAAAAAkQGULCwBDAAAgQUEqD1wLBgBBKQ+nCwcAQSgPQAAL +invalid invalid return.wast:315 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAQQACQA8LCw== +invalid invalid return.wast:324 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAQQADQA8LCw== +invalid invalid return.wast:333 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQQBBAARADwsL +invalid invalid return.wast:342 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AQQBBAAR/QQAFDwsaCw== +invalid invalid return.wast:351 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQQACQA8MAAsL +invalid invalid return.wast:360 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAQQACQA9BAQ0ACws= +invalid invalid return.wast:369 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAQQACQA8OAAALCw== +invalid invalid return.wast:378 AGFzbQEAAAABBQFgAAF/AwIBAAoGAQQADw8L +invalid invalid return.wast:386 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAD0EBQQIbCw== +invalid invalid return.wast:394 AGFzbQEAAAABCgJgAAF/YAF/AX8DAwIAAQoMAgUADxABCwQAIAAL +invalid invalid return.wast:403 AGFzbQEAAAABCgJgAX8Bf2AAAX8DAwIAAQQFAXABAQEJBwEAQQALAQAKEgIEACAACwsAAn8PQQARAAALCw== +invalid invalid return.wast:418 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkBAX8PIQAgAAs= +invalid invalid return.wast:427 AGFzbQEAAAABBQFgAAF/AwIBAAoJAQcBAX8PIgAL +invalid invalid return.wast:436 AGFzbQEAAAABBQFgAAF/AwIBAAYGAX8BQQALCgkBBwAPJAAjAAs= +invalid invalid return.wast:445 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQAACgcBBQAPQAAL +invalid invalid return.wast:454 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQAACggBBgAPKAIACw== +invalid invalid return.wast:463 AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABCgoBCAAPQQE2AgAL +invalid invalid return.wast:472 AGFzbQEAAAABBQFgAAF8AwIBAAoGAQQAAQ8L +invalid invalid return.wast:476 AGFzbQEAAAABBQFgAAF8AwIBAAoHAQUAQgEPCw== +valid valid select.wast:1 AGFzbQEAAAABPQpgAn9/AX9gAABgA39/fwF/YAN+fn8BfmADfX1/AX1gA3x8fwF8YANwcH8BcGADb29/AW9gAX8Bf2ABfwADLy4BAgMEBQIDBAUGBwgICAgICAkICAgICAgACAgICQkICAgICQgICAgICAgICAgIBAkCcAEBAXABAQEFAwEAAQYGAX8BQQoLB/AFKwpzZWxlY3QtaTMyAAEKc2VsZWN0LWk2NAACCnNlbGVjdC1mMzIAAwpzZWxlY3QtZjY0AAQMc2VsZWN0LWkzMi10AAUMc2VsZWN0LWk2NC10AAYMc2VsZWN0LWYzMi10AAcMc2VsZWN0LWY2NC10AAgOc2VsZWN0LWZ1bmNyZWYACRBzZWxlY3QtZXh0ZXJucmVmAAoPYXMtc2VsZWN0LWZpcnN0AAsNYXMtc2VsZWN0LW1pZAAMDmFzLXNlbGVjdC1sYXN0AA0NYXMtbG9vcC1maXJzdAAOC2FzLWxvb3AtbWlkAA8MYXMtbG9vcC1sYXN0ABAPYXMtaWYtY29uZGl0aW9uABEKYXMtaWYtdGhlbgASCmFzLWlmLWVsc2UAEw5hcy1icl9pZi1maXJzdAAUDWFzLWJyX2lmLWxhc3QAFRFhcy1icl90YWJsZS1maXJzdAAWEGFzLWJyX3RhYmxlLWxhc3QAFxZhcy1jYWxsX2luZGlyZWN0LWZpcnN0ABkUYXMtY2FsbF9pbmRpcmVjdC1taWQAGhVhcy1jYWxsX2luZGlyZWN0LWxhc3QAGw5hcy1zdG9yZS1maXJzdAAcDWFzLXN0b3JlLWxhc3QAHRRhcy1tZW1vcnkuZ3Jvdy12YWx1ZQAeDWFzLWNhbGwtdmFsdWUAIA9hcy1yZXR1cm4tdmFsdWUAIQ9hcy1kcm9wLW9wZXJhbmQAIgthcy1ici12YWx1ZQAjEmFzLWxvY2FsLnNldC12YWx1ZQAkEmFzLWxvY2FsLnRlZS12YWx1ZQAlE2FzLWdsb2JhbC5zZXQtdmFsdWUAJg9hcy1sb2FkLW9wZXJhbmQAJxBhcy11bmFyeS1vcGVyYW5kACgRYXMtYmluYXJ5LW9wZXJhbmQAKQ9hcy10ZXN0LW9wZXJhbmQAKg9hcy1jb21wYXJlLWxlZnQAKxBhcy1jb21wYXJlLXJpZ2h0ACwSYXMtY29udmVydC1vcGVyYW5kAC0JDwIAQQALAQACAUEACwABGAqABS4CAAsJACAAIAEgAhsLCQAgACABIAIbCwkAIAAgASACGwsJACAAIAEgAhsLCwAgACABIAIcAX8LCwAgACABIAIcAX4LCwAgACABIAIcAX0LCwAgACABIAIcAXwLCwAgACABIAIcAXALCwAgACABIAIcAW8LDgBBAEEBIAAbQQJBAxsLDgBBAkEAQQEgABtBAxsLDgBBAkEDQQBBASAAGxsLEAADf0ECQQMgABsQABAACwsQAAN/EABBAkEDIAAbEAALCxAAA38QABAAQQJBAyAAGwsLDgBBAkEDIAAbBEAQAAsLEQBBAQR/QQJBAyAAGwVBBAsLEQBBAAR/QQIFQQJBAyAAGwsLEAACf0ECQQMgABtBBA0ACwsQAAJ/QQJBAkEDIAAbDQALCxIAAn9BAkEDIAAbQQIOAQAACwsSAAJ/QQJBAkEDIAAbDgEAAAsLBAAgAAsTAAJ/QQJBAyAAG0EBQQARAAELCxMAAn9BAUECQQMgABtBABEAAQsLEwACf0EBQQRBAkEDIAAbEQABCwsOAEEAQQQgABtBATYCAAsOAEEIQQFBAiAAGzYCAAsLAEEBQQIgABtAAAsEACAACwsAQQFBAiAAGxAfCwoAQQFBAiAAGw8LCgBBAUECIAAbGgsOAAJ/QQFBAiAAGwwACwsPAQF/QQFBAiAAGyEAIAALCwBBAUECIAAbIgALDQBBAUECIAAbJAAjAAsMAEEAQQQgABsoAgALCgBBAEEBIAAbRQsRAEEBQQIgABtBAUECIAAbbAsNAAJ/QQBBASAAG0ULCw8AAn9BAUECIAAbQQFMCwsPAAJ/QQFBAEEBIAAbRwsLDQACf0IBQgAgABunCws= +invalid invalid select.wast:320 AGFzbQEAAAABBAFgAAADAgEACgkBBwABAUEBGws= +invalid invalid select.wast:328 AGFzbQEAAAABBgFgAAJ/fwMCAQAKEgEQAEEAQQBBAEEAQQEcAn9/Cw== +invalid invalid select.wast:340 AGFzbQEAAAABBQFgAW8AAwIBAAoMAQoAIAAgAEEBGxoL +invalid invalid select.wast:347 AGFzbQEAAAABBAFgAAADAgEACgwBCgBBAUIBQQEbGgs= +invalid invalid select.wast:353 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAUMAAIA/QQEbGgs= +invalid invalid select.wast:359 AGFzbQEAAAABBAFgAAADAgEAChMBEQBBAUQAAAAAAADwP0EBGxoL +invalid invalid select.wast:388 AGFzbQEAAAABBAFgAAADAgEACgYBBAAbGgs= +invalid invalid select.wast:396 AGFzbQEAAAABBAFgAAADAgEACggBBgBBABsaCw== +invalid invalid select.wast:404 AGFzbQEAAAABBAFgAAADAgEACgoBCABBAEEAGxoL +invalid invalid select.wast:412 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEAQQACQBsaCws= +invalid invalid select.wast:421 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEAAkBBABsaCws= +invalid invalid select.wast:430 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAAJAQQBBABsaCws= +invalid invalid select.wast:439 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEAQQADQBsaCws= +invalid invalid select.wast:448 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEAA0BBABsaCws= +invalid invalid select.wast:457 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAANAQQBBABsaCws= +invalid invalid select.wast:466 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEAQQAEQBsaCws= +invalid invalid select.wast:475 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAEEABEBBABsaCws= +invalid invalid select.wast:484 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAARAQQBBABsaCws= +invalid invalid select.wast:496 AGFzbQEAAAABBAFgAAADAgEACgwBCgBBAUEBQgEbGgs= +invalid invalid select.wast:500 AGFzbQEAAAABBAFgAAADAgEACg8BDQBBAUEBQwAAgD8bGgs= +invalid invalid select.wast:504 AGFzbQEAAAABBAFgAAADAgEAChMBEQBBAUEBRAAAAAAAAPA/GxoL +invalid invalid select.wast:511 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAQgFCAUEBGws= +valid valid select.wast:518 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAAEBAFwAAEKUAsEAAAbCwUAABsBCwUAABsbCwUAABsbCwYAABsbGwsGAAAcAX8LBgAAHAF/CwcAABwBfxsLCQAAHAF/HAF/CwcAABsRAQALCAAAGxEBABsL +valid valid skip-stack-guard-page.wast:2 AGFzbQEAAAABCAJgAX8AYAAAAwMCAAEFAwEAAQcYARR0ZXN0LWd1YXJkLXBhZ2Utc2tpcAAACuCQAQIUACAAQQBGBEAQAQUgAEEBaxAACwvHkAEBoAh+EAFBACkAACEAQQApAAEhAUEAKQACIQJBACkAAyEDQQApAAQhBEEAKQAFIQVBACkABiEGQQApAAchB0EAKQAIIQhBACkACSEJQQApAAohCkEAKQALIQtBACkADCEMQQApAA0hDUEAKQAOIQ5BACkADyEPQQApABAhEEEAKQARIRFBACkAEiESQQApABMhE0EAKQAUIRRBACkAFSEVQQApABYhFkEAKQAXIRdBACkAGCEYQQApABkhGUEAKQAaIRpBACkAGyEbQQApABwhHEEAKQAdIR1BACkAHiEeQQApAB8hH0EAKQAgISBBACkAISEhQQApACIhIkEAKQAjISNBACkAJCEkQQApACUhJUEAKQAmISZBACkAJyEnQQApACghKEEAKQApISlBACkAKiEqQQApACshK0EAKQAsISxBACkALSEtQQApAC4hLkEAKQAvIS9BACkAMCEwQQApADEhMUEAKQAyITJBACkAMyEzQQApADQhNEEAKQA1ITVBACkANiE2QQApADchN0EAKQA4IThBACkAOSE5QQApADohOkEAKQA7ITtBACkAPCE8QQApAD0hPUEAKQA+IT5BACkAPyE/QQApAEAhQEEAKQBBIUFBACkAQiFCQQApAEMhQ0EAKQBEIURBACkARSFFQQApAEYhRkEAKQBHIUdBACkASCFIQQApAEkhSUEAKQBKIUpBACkASyFLQQApAEwhTEEAKQBNIU1BACkATiFOQQApAE8hT0EAKQBQIVBBACkAUSFRQQApAFIhUkEAKQBTIVNBACkAVCFUQQApAFUhVUEAKQBWIVZBACkAVyFXQQApAFghWEEAKQBZIVlBACkAWiFaQQApAFshW0EAKQBcIVxBACkAXSFdQQApAF4hXkEAKQBfIV9BACkAYCFgQQApAGEhYUEAKQBiIWJBACkAYyFjQQApAGQhZEEAKQBlIWVBACkAZiFmQQApAGchZ0EAKQBoIWhBACkAaSFpQQApAGohakEAKQBrIWtBACkAbCFsQQApAG0hbUEAKQBuIW5BACkAbyFvQQApAHAhcEEAKQBxIXFBACkAciFyQQApAHMhc0EAKQB0IXRBACkAdSF1QQApAHYhdkEAKQB3IXdBACkAeCF4QQApAHkheUEAKQB6IXpBACkAeyF7QQApAHwhfEEAKQB9IX1BACkAfiF+QQApAH8hf0EAKQCAASGAAUEAKQCBASGBAUEAKQCCASGCAUEAKQCDASGDAUEAKQCEASGEAUEAKQCFASGFAUEAKQCGASGGAUEAKQCHASGHAUEAKQCIASGIAUEAKQCJASGJAUEAKQCKASGKAUEAKQCLASGLAUEAKQCMASGMAUEAKQCNASGNAUEAKQCOASGOAUEAKQCPASGPAUEAKQCQASGQAUEAKQCRASGRAUEAKQCSASGSAUEAKQCTASGTAUEAKQCUASGUAUEAKQCVASGVAUEAKQCWASGWAUEAKQCXASGXAUEAKQCYASGYAUEAKQCZASGZAUEAKQCaASGaAUEAKQCbASGbAUEAKQCcASGcAUEAKQCdASGdAUEAKQCeASGeAUEAKQCfASGfAUEAKQCgASGgAUEAKQChASGhAUEAKQCiASGiAUEAKQCjASGjAUEAKQCkASGkAUEAKQClASGlAUEAKQCmASGmAUEAKQCnASGnAUEAKQCoASGoAUEAKQCpASGpAUEAKQCqASGqAUEAKQCrASGrAUEAKQCsASGsAUEAKQCtASGtAUEAKQCuASGuAUEAKQCvASGvAUEAKQCwASGwAUEAKQCxASGxAUEAKQCyASGyAUEAKQCzASGzAUEAKQC0ASG0AUEAKQC1ASG1AUEAKQC2ASG2AUEAKQC3ASG3AUEAKQC4ASG4AUEAKQC5ASG5AUEAKQC6ASG6AUEAKQC7ASG7AUEAKQC8ASG8AUEAKQC9ASG9AUEAKQC+ASG+AUEAKQC/ASG/AUEAKQDAASHAAUEAKQDBASHBAUEAKQDCASHCAUEAKQDDASHDAUEAKQDEASHEAUEAKQDFASHFAUEAKQDGASHGAUEAKQDHASHHAUEAKQDIASHIAUEAKQDJASHJAUEAKQDKASHKAUEAKQDLASHLAUEAKQDMASHMAUEAKQDNASHNAUEAKQDOASHOAUEAKQDPASHPAUEAKQDQASHQAUEAKQDRASHRAUEAKQDSASHSAUEAKQDTASHTAUEAKQDUASHUAUEAKQDVASHVAUEAKQDWASHWAUEAKQDXASHXAUEAKQDYASHYAUEAKQDZASHZAUEAKQDaASHaAUEAKQDbASHbAUEAKQDcASHcAUEAKQDdASHdAUEAKQDeASHeAUEAKQDfASHfAUEAKQDgASHgAUEAKQDhASHhAUEAKQDiASHiAUEAKQDjASHjAUEAKQDkASHkAUEAKQDlASHlAUEAKQDmASHmAUEAKQDnASHnAUEAKQDoASHoAUEAKQDpASHpAUEAKQDqASHqAUEAKQDrASHrAUEAKQDsASHsAUEAKQDtASHtAUEAKQDuASHuAUEAKQDvASHvAUEAKQDwASHwAUEAKQDxASHxAUEAKQDyASHyAUEAKQDzASHzAUEAKQD0ASH0AUEAKQD1ASH1AUEAKQD2ASH2AUEAKQD3ASH3AUEAKQD4ASH4AUEAKQD5ASH5AUEAKQD6ASH6AUEAKQD7ASH7AUEAKQD8ASH8AUEAKQD9ASH9AUEAKQD+ASH+AUEAKQD/ASH/AUEAKQCAAiGAAkEAKQCBAiGBAkEAKQCCAiGCAkEAKQCDAiGDAkEAKQCEAiGEAkEAKQCFAiGFAkEAKQCGAiGGAkEAKQCHAiGHAkEAKQCIAiGIAkEAKQCJAiGJAkEAKQCKAiGKAkEAKQCLAiGLAkEAKQCMAiGMAkEAKQCNAiGNAkEAKQCOAiGOAkEAKQCPAiGPAkEAKQCQAiGQAkEAKQCRAiGRAkEAKQCSAiGSAkEAKQCTAiGTAkEAKQCUAiGUAkEAKQCVAiGVAkEAKQCWAiGWAkEAKQCXAiGXAkEAKQCYAiGYAkEAKQCZAiGZAkEAKQCaAiGaAkEAKQCbAiGbAkEAKQCcAiGcAkEAKQCdAiGdAkEAKQCeAiGeAkEAKQCfAiGfAkEAKQCgAiGgAkEAKQChAiGhAkEAKQCiAiGiAkEAKQCjAiGjAkEAKQCkAiGkAkEAKQClAiGlAkEAKQCmAiGmAkEAKQCnAiGnAkEAKQCoAiGoAkEAKQCpAiGpAkEAKQCqAiGqAkEAKQCrAiGrAkEAKQCsAiGsAkEAKQCtAiGtAkEAKQCuAiGuAkEAKQCvAiGvAkEAKQCwAiGwAkEAKQCxAiGxAkEAKQCyAiGyAkEAKQCzAiGzAkEAKQC0AiG0AkEAKQC1AiG1AkEAKQC2AiG2AkEAKQC3AiG3AkEAKQC4AiG4AkEAKQC5AiG5AkEAKQC6AiG6AkEAKQC7AiG7AkEAKQC8AiG8AkEAKQC9AiG9AkEAKQC+AiG+AkEAKQC/AiG/AkEAKQDAAiHAAkEAKQDBAiHBAkEAKQDCAiHCAkEAKQDDAiHDAkEAKQDEAiHEAkEAKQDFAiHFAkEAKQDGAiHGAkEAKQDHAiHHAkEAKQDIAiHIAkEAKQDJAiHJAkEAKQDKAiHKAkEAKQDLAiHLAkEAKQDMAiHMAkEAKQDNAiHNAkEAKQDOAiHOAkEAKQDPAiHPAkEAKQDQAiHQAkEAKQDRAiHRAkEAKQDSAiHSAkEAKQDTAiHTAkEAKQDUAiHUAkEAKQDVAiHVAkEAKQDWAiHWAkEAKQDXAiHXAkEAKQDYAiHYAkEAKQDZAiHZAkEAKQDaAiHaAkEAKQDbAiHbAkEAKQDcAiHcAkEAKQDdAiHdAkEAKQDeAiHeAkEAKQDfAiHfAkEAKQDgAiHgAkEAKQDhAiHhAkEAKQDiAiHiAkEAKQDjAiHjAkEAKQDkAiHkAkEAKQDlAiHlAkEAKQDmAiHmAkEAKQDnAiHnAkEAKQDoAiHoAkEAKQDpAiHpAkEAKQDqAiHqAkEAKQDrAiHrAkEAKQDsAiHsAkEAKQDtAiHtAkEAKQDuAiHuAkEAKQDvAiHvAkEAKQDwAiHwAkEAKQDxAiHxAkEAKQDyAiHyAkEAKQDzAiHzAkEAKQD0AiH0AkEAKQD1AiH1AkEAKQD2AiH2AkEAKQD3AiH3AkEAKQD4AiH4AkEAKQD5AiH5AkEAKQD6AiH6AkEAKQD7AiH7AkEAKQD8AiH8AkEAKQD9AiH9AkEAKQD+AiH+AkEAKQD/AiH/AkEAKQCAAyGAA0EAKQCBAyGBA0EAKQCCAyGCA0EAKQCDAyGDA0EAKQCEAyGEA0EAKQCFAyGFA0EAKQCGAyGGA0EAKQCHAyGHA0EAKQCIAyGIA0EAKQCJAyGJA0EAKQCKAyGKA0EAKQCLAyGLA0EAKQCMAyGMA0EAKQCNAyGNA0EAKQCOAyGOA0EAKQCPAyGPA0EAKQCQAyGQA0EAKQCRAyGRA0EAKQCSAyGSA0EAKQCTAyGTA0EAKQCUAyGUA0EAKQCVAyGVA0EAKQCWAyGWA0EAKQCXAyGXA0EAKQCYAyGYA0EAKQCZAyGZA0EAKQCaAyGaA0EAKQCbAyGbA0EAKQCcAyGcA0EAKQCdAyGdA0EAKQCeAyGeA0EAKQCfAyGfA0EAKQCgAyGgA0EAKQChAyGhA0EAKQCiAyGiA0EAKQCjAyGjA0EAKQCkAyGkA0EAKQClAyGlA0EAKQCmAyGmA0EAKQCnAyGnA0EAKQCoAyGoA0EAKQCpAyGpA0EAKQCqAyGqA0EAKQCrAyGrA0EAKQCsAyGsA0EAKQCtAyGtA0EAKQCuAyGuA0EAKQCvAyGvA0EAKQCwAyGwA0EAKQCxAyGxA0EAKQCyAyGyA0EAKQCzAyGzA0EAKQC0AyG0A0EAKQC1AyG1A0EAKQC2AyG2A0EAKQC3AyG3A0EAKQC4AyG4A0EAKQC5AyG5A0EAKQC6AyG6A0EAKQC7AyG7A0EAKQC8AyG8A0EAKQC9AyG9A0EAKQC+AyG+A0EAKQC/AyG/A0EAKQDAAyHAA0EAKQDBAyHBA0EAKQDCAyHCA0EAKQDDAyHDA0EAKQDEAyHEA0EAKQDFAyHFA0EAKQDGAyHGA0EAKQDHAyHHA0EAKQDIAyHIA0EAKQDJAyHJA0EAKQDKAyHKA0EAKQDLAyHLA0EAKQDMAyHMA0EAKQDNAyHNA0EAKQDOAyHOA0EAKQDPAyHPA0EAKQDQAyHQA0EAKQDRAyHRA0EAKQDSAyHSA0EAKQDTAyHTA0EAKQDUAyHUA0EAKQDVAyHVA0EAKQDWAyHWA0EAKQDXAyHXA0EAKQDYAyHYA0EAKQDZAyHZA0EAKQDaAyHaA0EAKQDbAyHbA0EAKQDcAyHcA0EAKQDdAyHdA0EAKQDeAyHeA0EAKQDfAyHfA0EAKQDgAyHgA0EAKQDhAyHhA0EAKQDiAyHiA0EAKQDjAyHjA0EAKQDkAyHkA0EAKQDlAyHlA0EAKQDmAyHmA0EAKQDnAyHnA0EAKQDoAyHoA0EAKQDpAyHpA0EAKQDqAyHqA0EAKQDrAyHrA0EAKQDsAyHsA0EAKQDtAyHtA0EAKQDuAyHuA0EAKQDvAyHvA0EAKQDwAyHwA0EAKQDxAyHxA0EAKQDyAyHyA0EAKQDzAyHzA0EAKQD0AyH0A0EAKQD1AyH1A0EAKQD2AyH2A0EAKQD3AyH3A0EAKQD4AyH4A0EAKQD5AyH5A0EAKQD6AyH6A0EAKQD7AyH7A0EAKQD8AyH8A0EAKQD9AyH9A0EAKQD+AyH+A0EAKQD/AyH/A0EAKQCABCGABEEAKQCBBCGBBEEAKQCCBCGCBEEAKQCDBCGDBEEAKQCEBCGEBEEAKQCFBCGFBEEAKQCGBCGGBEEAKQCHBCGHBEEAKQCIBCGIBEEAKQCJBCGJBEEAKQCKBCGKBEEAKQCLBCGLBEEAKQCMBCGMBEEAKQCNBCGNBEEAKQCOBCGOBEEAKQCPBCGPBEEAKQCQBCGQBEEAKQCRBCGRBEEAKQCSBCGSBEEAKQCTBCGTBEEAKQCUBCGUBEEAKQCVBCGVBEEAKQCWBCGWBEEAKQCXBCGXBEEAKQCYBCGYBEEAKQCZBCGZBEEAKQCaBCGaBEEAKQCbBCGbBEEAKQCcBCGcBEEAKQCdBCGdBEEAKQCeBCGeBEEAKQCfBCGfBEEAKQCgBCGgBEEAKQChBCGhBEEAKQCiBCGiBEEAKQCjBCGjBEEAKQCkBCGkBEEAKQClBCGlBEEAKQCmBCGmBEEAKQCnBCGnBEEAKQCoBCGoBEEAKQCpBCGpBEEAKQCqBCGqBEEAKQCrBCGrBEEAKQCsBCGsBEEAKQCtBCGtBEEAKQCuBCGuBEEAKQCvBCGvBEEAKQCwBCGwBEEAKQCxBCGxBEEAKQCyBCGyBEEAKQCzBCGzBEEAKQC0BCG0BEEAKQC1BCG1BEEAKQC2BCG2BEEAKQC3BCG3BEEAKQC4BCG4BEEAKQC5BCG5BEEAKQC6BCG6BEEAKQC7BCG7BEEAKQC8BCG8BEEAKQC9BCG9BEEAKQC+BCG+BEEAKQC/BCG/BEEAKQDABCHABEEAKQDBBCHBBEEAKQDCBCHCBEEAKQDDBCHDBEEAKQDEBCHEBEEAKQDFBCHFBEEAKQDGBCHGBEEAKQDHBCHHBEEAKQDIBCHIBEEAKQDJBCHJBEEAKQDKBCHKBEEAKQDLBCHLBEEAKQDMBCHMBEEAKQDNBCHNBEEAKQDOBCHOBEEAKQDPBCHPBEEAKQDQBCHQBEEAKQDRBCHRBEEAKQDSBCHSBEEAKQDTBCHTBEEAKQDUBCHUBEEAKQDVBCHVBEEAKQDWBCHWBEEAKQDXBCHXBEEAKQDYBCHYBEEAKQDZBCHZBEEAKQDaBCHaBEEAKQDbBCHbBEEAKQDcBCHcBEEAKQDdBCHdBEEAKQDeBCHeBEEAKQDfBCHfBEEAKQDgBCHgBEEAKQDhBCHhBEEAKQDiBCHiBEEAKQDjBCHjBEEAKQDkBCHkBEEAKQDlBCHlBEEAKQDmBCHmBEEAKQDnBCHnBEEAKQDoBCHoBEEAKQDpBCHpBEEAKQDqBCHqBEEAKQDrBCHrBEEAKQDsBCHsBEEAKQDtBCHtBEEAKQDuBCHuBEEAKQDvBCHvBEEAKQDwBCHwBEEAKQDxBCHxBEEAKQDyBCHyBEEAKQDzBCHzBEEAKQD0BCH0BEEAKQD1BCH1BEEAKQD2BCH2BEEAKQD3BCH3BEEAKQD4BCH4BEEAKQD5BCH5BEEAKQD6BCH6BEEAKQD7BCH7BEEAKQD8BCH8BEEAKQD9BCH9BEEAKQD+BCH+BEEAKQD/BCH/BEEAKQCABSGABUEAKQCBBSGBBUEAKQCCBSGCBUEAKQCDBSGDBUEAKQCEBSGEBUEAKQCFBSGFBUEAKQCGBSGGBUEAKQCHBSGHBUEAKQCIBSGIBUEAKQCJBSGJBUEAKQCKBSGKBUEAKQCLBSGLBUEAKQCMBSGMBUEAKQCNBSGNBUEAKQCOBSGOBUEAKQCPBSGPBUEAKQCQBSGQBUEAKQCRBSGRBUEAKQCSBSGSBUEAKQCTBSGTBUEAKQCUBSGUBUEAKQCVBSGVBUEAKQCWBSGWBUEAKQCXBSGXBUEAKQCYBSGYBUEAKQCZBSGZBUEAKQCaBSGaBUEAKQCbBSGbBUEAKQCcBSGcBUEAKQCdBSGdBUEAKQCeBSGeBUEAKQCfBSGfBUEAKQCgBSGgBUEAKQChBSGhBUEAKQCiBSGiBUEAKQCjBSGjBUEAKQCkBSGkBUEAKQClBSGlBUEAKQCmBSGmBUEAKQCnBSGnBUEAKQCoBSGoBUEAKQCpBSGpBUEAKQCqBSGqBUEAKQCrBSGrBUEAKQCsBSGsBUEAKQCtBSGtBUEAKQCuBSGuBUEAKQCvBSGvBUEAKQCwBSGwBUEAKQCxBSGxBUEAKQCyBSGyBUEAKQCzBSGzBUEAKQC0BSG0BUEAKQC1BSG1BUEAKQC2BSG2BUEAKQC3BSG3BUEAKQC4BSG4BUEAKQC5BSG5BUEAKQC6BSG6BUEAKQC7BSG7BUEAKQC8BSG8BUEAKQC9BSG9BUEAKQC+BSG+BUEAKQC/BSG/BUEAKQDABSHABUEAKQDBBSHBBUEAKQDCBSHCBUEAKQDDBSHDBUEAKQDEBSHEBUEAKQDFBSHFBUEAKQDGBSHGBUEAKQDHBSHHBUEAKQDIBSHIBUEAKQDJBSHJBUEAKQDKBSHKBUEAKQDLBSHLBUEAKQDMBSHMBUEAKQDNBSHNBUEAKQDOBSHOBUEAKQDPBSHPBUEAKQDQBSHQBUEAKQDRBSHRBUEAKQDSBSHSBUEAKQDTBSHTBUEAKQDUBSHUBUEAKQDVBSHVBUEAKQDWBSHWBUEAKQDXBSHXBUEAKQDYBSHYBUEAKQDZBSHZBUEAKQDaBSHaBUEAKQDbBSHbBUEAKQDcBSHcBUEAKQDdBSHdBUEAKQDeBSHeBUEAKQDfBSHfBUEAKQDgBSHgBUEAKQDhBSHhBUEAKQDiBSHiBUEAKQDjBSHjBUEAKQDkBSHkBUEAKQDlBSHlBUEAKQDmBSHmBUEAKQDnBSHnBUEAKQDoBSHoBUEAKQDpBSHpBUEAKQDqBSHqBUEAKQDrBSHrBUEAKQDsBSHsBUEAKQDtBSHtBUEAKQDuBSHuBUEAKQDvBSHvBUEAKQDwBSHwBUEAKQDxBSHxBUEAKQDyBSHyBUEAKQDzBSHzBUEAKQD0BSH0BUEAKQD1BSH1BUEAKQD2BSH2BUEAKQD3BSH3BUEAKQD4BSH4BUEAKQD5BSH5BUEAKQD6BSH6BUEAKQD7BSH7BUEAKQD8BSH8BUEAKQD9BSH9BUEAKQD+BSH+BUEAKQD/BSH/BUEAKQCABiGABkEAKQCBBiGBBkEAKQCCBiGCBkEAKQCDBiGDBkEAKQCEBiGEBkEAKQCFBiGFBkEAKQCGBiGGBkEAKQCHBiGHBkEAKQCIBiGIBkEAKQCJBiGJBkEAKQCKBiGKBkEAKQCLBiGLBkEAKQCMBiGMBkEAKQCNBiGNBkEAKQCOBiGOBkEAKQCPBiGPBkEAKQCQBiGQBkEAKQCRBiGRBkEAKQCSBiGSBkEAKQCTBiGTBkEAKQCUBiGUBkEAKQCVBiGVBkEAKQCWBiGWBkEAKQCXBiGXBkEAKQCYBiGYBkEAKQCZBiGZBkEAKQCaBiGaBkEAKQCbBiGbBkEAKQCcBiGcBkEAKQCdBiGdBkEAKQCeBiGeBkEAKQCfBiGfBkEAKQCgBiGgBkEAKQChBiGhBkEAKQCiBiGiBkEAKQCjBiGjBkEAKQCkBiGkBkEAKQClBiGlBkEAKQCmBiGmBkEAKQCnBiGnBkEAKQCoBiGoBkEAKQCpBiGpBkEAKQCqBiGqBkEAKQCrBiGrBkEAKQCsBiGsBkEAKQCtBiGtBkEAKQCuBiGuBkEAKQCvBiGvBkEAKQCwBiGwBkEAKQCxBiGxBkEAKQCyBiGyBkEAKQCzBiGzBkEAKQC0BiG0BkEAKQC1BiG1BkEAKQC2BiG2BkEAKQC3BiG3BkEAKQC4BiG4BkEAKQC5BiG5BkEAKQC6BiG6BkEAKQC7BiG7BkEAKQC8BiG8BkEAKQC9BiG9BkEAKQC+BiG+BkEAKQC/BiG/BkEAKQDABiHABkEAKQDBBiHBBkEAKQDCBiHCBkEAKQDDBiHDBkEAKQDEBiHEBkEAKQDFBiHFBkEAKQDGBiHGBkEAKQDHBiHHBkEAKQDIBiHIBkEAKQDJBiHJBkEAKQDKBiHKBkEAKQDLBiHLBkEAKQDMBiHMBkEAKQDNBiHNBkEAKQDOBiHOBkEAKQDPBiHPBkEAKQDQBiHQBkEAKQDRBiHRBkEAKQDSBiHSBkEAKQDTBiHTBkEAKQDUBiHUBkEAKQDVBiHVBkEAKQDWBiHWBkEAKQDXBiHXBkEAKQDYBiHYBkEAKQDZBiHZBkEAKQDaBiHaBkEAKQDbBiHbBkEAKQDcBiHcBkEAKQDdBiHdBkEAKQDeBiHeBkEAKQDfBiHfBkEAKQDgBiHgBkEAKQDhBiHhBkEAKQDiBiHiBkEAKQDjBiHjBkEAKQDkBiHkBkEAKQDlBiHlBkEAKQDmBiHmBkEAKQDnBiHnBkEAKQDoBiHoBkEAKQDpBiHpBkEAKQDqBiHqBkEAKQDrBiHrBkEAKQDsBiHsBkEAKQDtBiHtBkEAKQDuBiHuBkEAKQDvBiHvBkEAKQDwBiHwBkEAKQDxBiHxBkEAKQDyBiHyBkEAKQDzBiHzBkEAKQD0BiH0BkEAKQD1BiH1BkEAKQD2BiH2BkEAKQD3BiH3BkEAKQD4BiH4BkEAKQD5BiH5BkEAKQD6BiH6BkEAKQD7BiH7BkEAKQD8BiH8BkEAKQD9BiH9BkEAKQD+BiH+BkEAKQD/BiH/BkEAKQCAByGAB0EAKQCBByGBB0EAKQCCByGCB0EAKQCDByGDB0EAKQCEByGEB0EAKQCFByGFB0EAKQCGByGGB0EAKQCHByGHB0EAKQCIByGIB0EAKQCJByGJB0EAKQCKByGKB0EAKQCLByGLB0EAKQCMByGMB0EAKQCNByGNB0EAKQCOByGOB0EAKQCPByGPB0EAKQCQByGQB0EAKQCRByGRB0EAKQCSByGSB0EAKQCTByGTB0EAKQCUByGUB0EAKQCVByGVB0EAKQCWByGWB0EAKQCXByGXB0EAKQCYByGYB0EAKQCZByGZB0EAKQCaByGaB0EAKQCbByGbB0EAKQCcByGcB0EAKQCdByGdB0EAKQCeByGeB0EAKQCfByGfB0EAKQCgByGgB0EAKQChByGhB0EAKQCiByGiB0EAKQCjByGjB0EAKQCkByGkB0EAKQClByGlB0EAKQCmByGmB0EAKQCnByGnB0EAKQCoByGoB0EAKQCpByGpB0EAKQCqByGqB0EAKQCrByGrB0EAKQCsByGsB0EAKQCtByGtB0EAKQCuByGuB0EAKQCvByGvB0EAKQCwByGwB0EAKQCxByGxB0EAKQCyByGyB0EAKQCzByGzB0EAKQC0ByG0B0EAKQC1ByG1B0EAKQC2ByG2B0EAKQC3ByG3B0EAKQC4ByG4B0EAKQC5ByG5B0EAKQC6ByG6B0EAKQC7ByG7B0EAKQC8ByG8B0EAKQC9ByG9B0EAKQC+ByG+B0EAKQC/ByG/B0EAKQDAByHAB0EAKQDBByHBB0EAKQDCByHCB0EAKQDDByHDB0EAKQDEByHEB0EAKQDFByHFB0EAKQDGByHGB0EAKQDHByHHB0EAKQDIByHIB0EAKQDJByHJB0EAKQDKByHKB0EAKQDLByHLB0EAKQDMByHMB0EAKQDNByHNB0EAKQDOByHOB0EAKQDPByHPB0EAKQDQByHQB0EAKQDRByHRB0EAKQDSByHSB0EAKQDTByHTB0EAKQDUByHUB0EAKQDVByHVB0EAKQDWByHWB0EAKQDXByHXB0EAKQDYByHYB0EAKQDZByHZB0EAKQDaByHaB0EAKQDbByHbB0EAKQDcByHcB0EAKQDdByHdB0EAKQDeByHeB0EAKQDfByHfB0EAKQDgByHgB0EAKQDhByHhB0EAKQDiByHiB0EAKQDjByHjB0EAKQDkByHkB0EAKQDlByHlB0EAKQDmByHmB0EAKQDnByHnB0EAKQDoByHoB0EAKQDpByHpB0EAKQDqByHqB0EAKQDrByHrB0EAKQDsByHsB0EAKQDtByHtB0EAKQDuByHuB0EAKQDvByHvB0EAKQDwByHwB0EAKQDxByHxB0EAKQDyByHyB0EAKQDzByHzB0EAKQD0ByH0B0EAKQD1ByH1B0EAKQD2ByH2B0EAKQD3ByH3B0EAKQD4ByH4B0EAKQD5ByH5B0EAKQD6ByH6B0EAKQD7ByH7B0EAKQD8ByH8B0EAKQD9ByH9B0EAKQD+ByH+B0EAKQD/ByH/B0EAKQCACCGACEEAKQCBCCGBCEEAKQCCCCGCCEEAKQCDCCGDCEEAKQCECCGECEEAKQCFCCGFCEEAKQCGCCGGCEEAKQCHCCGHCEEAKQCICCGICEEAKQCJCCGJCEEAKQCKCCGKCEEAKQCLCCGLCEEAKQCMCCGMCEEAKQCNCCGNCEEAKQCOCCGOCEEAKQCPCCGPCEEAKQCQCCGQCEEAKQCRCCGRCEEAKQCSCCGSCEEAKQCTCCGTCEEAKQCUCCGUCEEAKQCVCCGVCEEAKQCWCCGWCEEAKQCXCCGXCEEAKQCYCCGYCEEAKQCZCCGZCEEAKQCaCCGaCEEAKQCbCCGbCEEAKQCcCCGcCEEAKQCdCCGdCEEAKQCeCCGeCEEAKQCfCCGfCEEAIAA3AABBACABNwABQQAgAjcAAkEAIAM3AANBACAENwAEQQAgBTcABUEAIAY3AAZBACAHNwAHQQAgCDcACEEAIAk3AAlBACAKNwAKQQAgCzcAC0EAIAw3AAxBACANNwANQQAgDjcADkEAIA83AA9BACAQNwAQQQAgETcAEUEAIBI3ABJBACATNwATQQAgFDcAFEEAIBU3ABVBACAWNwAWQQAgFzcAF0EAIBg3ABhBACAZNwAZQQAgGjcAGkEAIBs3ABtBACAcNwAcQQAgHTcAHUEAIB43AB5BACAfNwAfQQAgIDcAIEEAICE3ACFBACAiNwAiQQAgIzcAI0EAICQ3ACRBACAlNwAlQQAgJjcAJkEAICc3ACdBACAoNwAoQQAgKTcAKUEAICo3ACpBACArNwArQQAgLDcALEEAIC03AC1BACAuNwAuQQAgLzcAL0EAIDA3ADBBACAxNwAxQQAgMjcAMkEAIDM3ADNBACA0NwA0QQAgNTcANUEAIDY3ADZBACA3NwA3QQAgODcAOEEAIDk3ADlBACA6NwA6QQAgOzcAO0EAIDw3ADxBACA9NwA9QQAgPjcAPkEAID83AD9BACBANwBAQQAgQTcAQUEAIEI3AEJBACBDNwBDQQAgRDcAREEAIEU3AEVBACBGNwBGQQAgRzcAR0EAIEg3AEhBACBJNwBJQQAgSjcASkEAIEs3AEtBACBMNwBMQQAgTTcATUEAIE43AE5BACBPNwBPQQAgUDcAUEEAIFE3AFFBACBSNwBSQQAgUzcAU0EAIFQ3AFRBACBVNwBVQQAgVjcAVkEAIFc3AFdBACBYNwBYQQAgWTcAWUEAIFo3AFpBACBbNwBbQQAgXDcAXEEAIF03AF1BACBeNwBeQQAgXzcAX0EAIGA3AGBBACBhNwBhQQAgYjcAYkEAIGM3AGNBACBkNwBkQQAgZTcAZUEAIGY3AGZBACBnNwBnQQAgaDcAaEEAIGk3AGlBACBqNwBqQQAgazcAa0EAIGw3AGxBACBtNwBtQQAgbjcAbkEAIG83AG9BACBwNwBwQQAgcTcAcUEAIHI3AHJBACBzNwBzQQAgdDcAdEEAIHU3AHVBACB2NwB2QQAgdzcAd0EAIHg3AHhBACB5NwB5QQAgejcAekEAIHs3AHtBACB8NwB8QQAgfTcAfUEAIH43AH5BACB/NwB/QQAggAE3AIABQQAggQE3AIEBQQAgggE3AIIBQQAggwE3AIMBQQAghAE3AIQBQQAghQE3AIUBQQAghgE3AIYBQQAghwE3AIcBQQAgiAE3AIgBQQAgiQE3AIkBQQAgigE3AIoBQQAgiwE3AIsBQQAgjAE3AIwBQQAgjQE3AI0BQQAgjgE3AI4BQQAgjwE3AI8BQQAgkAE3AJABQQAgkQE3AJEBQQAgkgE3AJIBQQAgkwE3AJMBQQAglAE3AJQBQQAglQE3AJUBQQAglgE3AJYBQQAglwE3AJcBQQAgmAE3AJgBQQAgmQE3AJkBQQAgmgE3AJoBQQAgmwE3AJsBQQAgnAE3AJwBQQAgnQE3AJ0BQQAgngE3AJ4BQQAgnwE3AJ8BQQAgoAE3AKABQQAgoQE3AKEBQQAgogE3AKIBQQAgowE3AKMBQQAgpAE3AKQBQQAgpQE3AKUBQQAgpgE3AKYBQQAgpwE3AKcBQQAgqAE3AKgBQQAgqQE3AKkBQQAgqgE3AKoBQQAgqwE3AKsBQQAgrAE3AKwBQQAgrQE3AK0BQQAgrgE3AK4BQQAgrwE3AK8BQQAgsAE3ALABQQAgsQE3ALEBQQAgsgE3ALIBQQAgswE3ALMBQQAgtAE3ALQBQQAgtQE3ALUBQQAgtgE3ALYBQQAgtwE3ALcBQQAguAE3ALgBQQAguQE3ALkBQQAgugE3ALoBQQAguwE3ALsBQQAgvAE3ALwBQQAgvQE3AL0BQQAgvgE3AL4BQQAgvwE3AL8BQQAgwAE3AMABQQAgwQE3AMEBQQAgwgE3AMIBQQAgwwE3AMMBQQAgxAE3AMQBQQAgxQE3AMUBQQAgxgE3AMYBQQAgxwE3AMcBQQAgyAE3AMgBQQAgyQE3AMkBQQAgygE3AMoBQQAgywE3AMsBQQAgzAE3AMwBQQAgzQE3AM0BQQAgzgE3AM4BQQAgzwE3AM8BQQAg0AE3ANABQQAg0QE3ANEBQQAg0gE3ANIBQQAg0wE3ANMBQQAg1AE3ANQBQQAg1QE3ANUBQQAg1gE3ANYBQQAg1wE3ANcBQQAg2AE3ANgBQQAg2QE3ANkBQQAg2gE3ANoBQQAg2wE3ANsBQQAg3AE3ANwBQQAg3QE3AN0BQQAg3gE3AN4BQQAg3wE3AN8BQQAg4AE3AOABQQAg4QE3AOEBQQAg4gE3AOIBQQAg4wE3AOMBQQAg5AE3AOQBQQAg5QE3AOUBQQAg5gE3AOYBQQAg5wE3AOcBQQAg6AE3AOgBQQAg6QE3AOkBQQAg6gE3AOoBQQAg6wE3AOsBQQAg7AE3AOwBQQAg7QE3AO0BQQAg7gE3AO4BQQAg7wE3AO8BQQAg8AE3APABQQAg8QE3APEBQQAg8gE3APIBQQAg8wE3APMBQQAg9AE3APQBQQAg9QE3APUBQQAg9gE3APYBQQAg9wE3APcBQQAg+AE3APgBQQAg+QE3APkBQQAg+gE3APoBQQAg+wE3APsBQQAg/AE3APwBQQAg/QE3AP0BQQAg/gE3AP4BQQAg/wE3AP8BQQAggAI3AIACQQAggQI3AIECQQAgggI3AIICQQAggwI3AIMCQQAghAI3AIQCQQAghQI3AIUCQQAghgI3AIYCQQAghwI3AIcCQQAgiAI3AIgCQQAgiQI3AIkCQQAgigI3AIoCQQAgiwI3AIsCQQAgjAI3AIwCQQAgjQI3AI0CQQAgjgI3AI4CQQAgjwI3AI8CQQAgkAI3AJACQQAgkQI3AJECQQAgkgI3AJICQQAgkwI3AJMCQQAglAI3AJQCQQAglQI3AJUCQQAglgI3AJYCQQAglwI3AJcCQQAgmAI3AJgCQQAgmQI3AJkCQQAgmgI3AJoCQQAgmwI3AJsCQQAgnAI3AJwCQQAgnQI3AJ0CQQAgngI3AJ4CQQAgnwI3AJ8CQQAgoAI3AKACQQAgoQI3AKECQQAgogI3AKICQQAgowI3AKMCQQAgpAI3AKQCQQAgpQI3AKUCQQAgpgI3AKYCQQAgpwI3AKcCQQAgqAI3AKgCQQAgqQI3AKkCQQAgqgI3AKoCQQAgqwI3AKsCQQAgrAI3AKwCQQAgrQI3AK0CQQAgrgI3AK4CQQAgrwI3AK8CQQAgsAI3ALACQQAgsQI3ALECQQAgsgI3ALICQQAgswI3ALMCQQAgtAI3ALQCQQAgtQI3ALUCQQAgtgI3ALYCQQAgtwI3ALcCQQAguAI3ALgCQQAguQI3ALkCQQAgugI3ALoCQQAguwI3ALsCQQAgvAI3ALwCQQAgvQI3AL0CQQAgvgI3AL4CQQAgvwI3AL8CQQAgwAI3AMACQQAgwQI3AMECQQAgwgI3AMICQQAgwwI3AMMCQQAgxAI3AMQCQQAgxQI3AMUCQQAgxgI3AMYCQQAgxwI3AMcCQQAgyAI3AMgCQQAgyQI3AMkCQQAgygI3AMoCQQAgywI3AMsCQQAgzAI3AMwCQQAgzQI3AM0CQQAgzgI3AM4CQQAgzwI3AM8CQQAg0AI3ANACQQAg0QI3ANECQQAg0gI3ANICQQAg0wI3ANMCQQAg1AI3ANQCQQAg1QI3ANUCQQAg1gI3ANYCQQAg1wI3ANcCQQAg2AI3ANgCQQAg2QI3ANkCQQAg2gI3ANoCQQAg2wI3ANsCQQAg3AI3ANwCQQAg3QI3AN0CQQAg3gI3AN4CQQAg3wI3AN8CQQAg4AI3AOACQQAg4QI3AOECQQAg4gI3AOICQQAg4wI3AOMCQQAg5AI3AOQCQQAg5QI3AOUCQQAg5gI3AOYCQQAg5wI3AOcCQQAg6AI3AOgCQQAg6QI3AOkCQQAg6gI3AOoCQQAg6wI3AOsCQQAg7AI3AOwCQQAg7QI3AO0CQQAg7gI3AO4CQQAg7wI3AO8CQQAg8AI3APACQQAg8QI3APECQQAg8gI3APICQQAg8wI3APMCQQAg9AI3APQCQQAg9QI3APUCQQAg9gI3APYCQQAg9wI3APcCQQAg+AI3APgCQQAg+QI3APkCQQAg+gI3APoCQQAg+wI3APsCQQAg/AI3APwCQQAg/QI3AP0CQQAg/gI3AP4CQQAg/wI3AP8CQQAggAM3AIADQQAggQM3AIEDQQAgggM3AIIDQQAggwM3AIMDQQAghAM3AIQDQQAghQM3AIUDQQAghgM3AIYDQQAghwM3AIcDQQAgiAM3AIgDQQAgiQM3AIkDQQAgigM3AIoDQQAgiwM3AIsDQQAgjAM3AIwDQQAgjQM3AI0DQQAgjgM3AI4DQQAgjwM3AI8DQQAgkAM3AJADQQAgkQM3AJEDQQAgkgM3AJIDQQAgkwM3AJMDQQAglAM3AJQDQQAglQM3AJUDQQAglgM3AJYDQQAglwM3AJcDQQAgmAM3AJgDQQAgmQM3AJkDQQAgmgM3AJoDQQAgmwM3AJsDQQAgnAM3AJwDQQAgnQM3AJ0DQQAgngM3AJ4DQQAgnwM3AJ8DQQAgoAM3AKADQQAgoQM3AKEDQQAgogM3AKIDQQAgowM3AKMDQQAgpAM3AKQDQQAgpQM3AKUDQQAgpgM3AKYDQQAgpwM3AKcDQQAgqAM3AKgDQQAgqQM3AKkDQQAgqgM3AKoDQQAgqwM3AKsDQQAgrAM3AKwDQQAgrQM3AK0DQQAgrgM3AK4DQQAgrwM3AK8DQQAgsAM3ALADQQAgsQM3ALEDQQAgsgM3ALIDQQAgswM3ALMDQQAgtAM3ALQDQQAgtQM3ALUDQQAgtgM3ALYDQQAgtwM3ALcDQQAguAM3ALgDQQAguQM3ALkDQQAgugM3ALoDQQAguwM3ALsDQQAgvAM3ALwDQQAgvQM3AL0DQQAgvgM3AL4DQQAgvwM3AL8DQQAgwAM3AMADQQAgwQM3AMEDQQAgwgM3AMIDQQAgwwM3AMMDQQAgxAM3AMQDQQAgxQM3AMUDQQAgxgM3AMYDQQAgxwM3AMcDQQAgyAM3AMgDQQAgyQM3AMkDQQAgygM3AMoDQQAgywM3AMsDQQAgzAM3AMwDQQAgzQM3AM0DQQAgzgM3AM4DQQAgzwM3AM8DQQAg0AM3ANADQQAg0QM3ANEDQQAg0gM3ANIDQQAg0wM3ANMDQQAg1AM3ANQDQQAg1QM3ANUDQQAg1gM3ANYDQQAg1wM3ANcDQQAg2AM3ANgDQQAg2QM3ANkDQQAg2gM3ANoDQQAg2wM3ANsDQQAg3AM3ANwDQQAg3QM3AN0DQQAg3gM3AN4DQQAg3wM3AN8DQQAg4AM3AOADQQAg4QM3AOEDQQAg4gM3AOIDQQAg4wM3AOMDQQAg5AM3AOQDQQAg5QM3AOUDQQAg5gM3AOYDQQAg5wM3AOcDQQAg6AM3AOgDQQAg6QM3AOkDQQAg6gM3AOoDQQAg6wM3AOsDQQAg7AM3AOwDQQAg7QM3AO0DQQAg7gM3AO4DQQAg7wM3AO8DQQAg8AM3APADQQAg8QM3APEDQQAg8gM3APIDQQAg8wM3APMDQQAg9AM3APQDQQAg9QM3APUDQQAg9gM3APYDQQAg9wM3APcDQQAg+AM3APgDQQAg+QM3APkDQQAg+gM3APoDQQAg+wM3APsDQQAg/AM3APwDQQAg/QM3AP0DQQAg/gM3AP4DQQAg/wM3AP8DQQAggAQ3AIAEQQAggQQ3AIEEQQAgggQ3AIIEQQAggwQ3AIMEQQAghAQ3AIQEQQAghQQ3AIUEQQAghgQ3AIYEQQAghwQ3AIcEQQAgiAQ3AIgEQQAgiQQ3AIkEQQAgigQ3AIoEQQAgiwQ3AIsEQQAgjAQ3AIwEQQAgjQQ3AI0EQQAgjgQ3AI4EQQAgjwQ3AI8EQQAgkAQ3AJAEQQAgkQQ3AJEEQQAgkgQ3AJIEQQAgkwQ3AJMEQQAglAQ3AJQEQQAglQQ3AJUEQQAglgQ3AJYEQQAglwQ3AJcEQQAgmAQ3AJgEQQAgmQQ3AJkEQQAgmgQ3AJoEQQAgmwQ3AJsEQQAgnAQ3AJwEQQAgnQQ3AJ0EQQAgngQ3AJ4EQQAgnwQ3AJ8EQQAgoAQ3AKAEQQAgoQQ3AKEEQQAgogQ3AKIEQQAgowQ3AKMEQQAgpAQ3AKQEQQAgpQQ3AKUEQQAgpgQ3AKYEQQAgpwQ3AKcEQQAgqAQ3AKgEQQAgqQQ3AKkEQQAgqgQ3AKoEQQAgqwQ3AKsEQQAgrAQ3AKwEQQAgrQQ3AK0EQQAgrgQ3AK4EQQAgrwQ3AK8EQQAgsAQ3ALAEQQAgsQQ3ALEEQQAgsgQ3ALIEQQAgswQ3ALMEQQAgtAQ3ALQEQQAgtQQ3ALUEQQAgtgQ3ALYEQQAgtwQ3ALcEQQAguAQ3ALgEQQAguQQ3ALkEQQAgugQ3ALoEQQAguwQ3ALsEQQAgvAQ3ALwEQQAgvQQ3AL0EQQAgvgQ3AL4EQQAgvwQ3AL8EQQAgwAQ3AMAEQQAgwQQ3AMEEQQAgwgQ3AMIEQQAgwwQ3AMMEQQAgxAQ3AMQEQQAgxQQ3AMUEQQAgxgQ3AMYEQQAgxwQ3AMcEQQAgyAQ3AMgEQQAgyQQ3AMkEQQAgygQ3AMoEQQAgywQ3AMsEQQAgzAQ3AMwEQQAgzQQ3AM0EQQAgzgQ3AM4EQQAgzwQ3AM8EQQAg0AQ3ANAEQQAg0QQ3ANEEQQAg0gQ3ANIEQQAg0wQ3ANMEQQAg1AQ3ANQEQQAg1QQ3ANUEQQAg1gQ3ANYEQQAg1wQ3ANcEQQAg2AQ3ANgEQQAg2QQ3ANkEQQAg2gQ3ANoEQQAg2wQ3ANsEQQAg3AQ3ANwEQQAg3QQ3AN0EQQAg3gQ3AN4EQQAg3wQ3AN8EQQAg4AQ3AOAEQQAg4QQ3AOEEQQAg4gQ3AOIEQQAg4wQ3AOMEQQAg5AQ3AOQEQQAg5QQ3AOUEQQAg5gQ3AOYEQQAg5wQ3AOcEQQAg6AQ3AOgEQQAg6QQ3AOkEQQAg6gQ3AOoEQQAg6wQ3AOsEQQAg7AQ3AOwEQQAg7QQ3AO0EQQAg7gQ3AO4EQQAg7wQ3AO8EQQAg8AQ3APAEQQAg8QQ3APEEQQAg8gQ3APIEQQAg8wQ3APMEQQAg9AQ3APQEQQAg9QQ3APUEQQAg9gQ3APYEQQAg9wQ3APcEQQAg+AQ3APgEQQAg+QQ3APkEQQAg+gQ3APoEQQAg+wQ3APsEQQAg/AQ3APwEQQAg/QQ3AP0EQQAg/gQ3AP4EQQAg/wQ3AP8EQQAggAU3AIAFQQAggQU3AIEFQQAgggU3AIIFQQAggwU3AIMFQQAghAU3AIQFQQAghQU3AIUFQQAghgU3AIYFQQAghwU3AIcFQQAgiAU3AIgFQQAgiQU3AIkFQQAgigU3AIoFQQAgiwU3AIsFQQAgjAU3AIwFQQAgjQU3AI0FQQAgjgU3AI4FQQAgjwU3AI8FQQAgkAU3AJAFQQAgkQU3AJEFQQAgkgU3AJIFQQAgkwU3AJMFQQAglAU3AJQFQQAglQU3AJUFQQAglgU3AJYFQQAglwU3AJcFQQAgmAU3AJgFQQAgmQU3AJkFQQAgmgU3AJoFQQAgmwU3AJsFQQAgnAU3AJwFQQAgnQU3AJ0FQQAgngU3AJ4FQQAgnwU3AJ8FQQAgoAU3AKAFQQAgoQU3AKEFQQAgogU3AKIFQQAgowU3AKMFQQAgpAU3AKQFQQAgpQU3AKUFQQAgpgU3AKYFQQAgpwU3AKcFQQAgqAU3AKgFQQAgqQU3AKkFQQAgqgU3AKoFQQAgqwU3AKsFQQAgrAU3AKwFQQAgrQU3AK0FQQAgrgU3AK4FQQAgrwU3AK8FQQAgsAU3ALAFQQAgsQU3ALEFQQAgsgU3ALIFQQAgswU3ALMFQQAgtAU3ALQFQQAgtQU3ALUFQQAgtgU3ALYFQQAgtwU3ALcFQQAguAU3ALgFQQAguQU3ALkFQQAgugU3ALoFQQAguwU3ALsFQQAgvAU3ALwFQQAgvQU3AL0FQQAgvgU3AL4FQQAgvwU3AL8FQQAgwAU3AMAFQQAgwQU3AMEFQQAgwgU3AMIFQQAgwwU3AMMFQQAgxAU3AMQFQQAgxQU3AMUFQQAgxgU3AMYFQQAgxwU3AMcFQQAgyAU3AMgFQQAgyQU3AMkFQQAgygU3AMoFQQAgywU3AMsFQQAgzAU3AMwFQQAgzQU3AM0FQQAgzgU3AM4FQQAgzwU3AM8FQQAg0AU3ANAFQQAg0QU3ANEFQQAg0gU3ANIFQQAg0wU3ANMFQQAg1AU3ANQFQQAg1QU3ANUFQQAg1gU3ANYFQQAg1wU3ANcFQQAg2AU3ANgFQQAg2QU3ANkFQQAg2gU3ANoFQQAg2wU3ANsFQQAg3AU3ANwFQQAg3QU3AN0FQQAg3gU3AN4FQQAg3wU3AN8FQQAg4AU3AOAFQQAg4QU3AOEFQQAg4gU3AOIFQQAg4wU3AOMFQQAg5AU3AOQFQQAg5QU3AOUFQQAg5gU3AOYFQQAg5wU3AOcFQQAg6AU3AOgFQQAg6QU3AOkFQQAg6gU3AOoFQQAg6wU3AOsFQQAg7AU3AOwFQQAg7QU3AO0FQQAg7gU3AO4FQQAg7wU3AO8FQQAg8AU3APAFQQAg8QU3APEFQQAg8gU3APIFQQAg8wU3APMFQQAg9AU3APQFQQAg9QU3APUFQQAg9gU3APYFQQAg9wU3APcFQQAg+AU3APgFQQAg+QU3APkFQQAg+gU3APoFQQAg+wU3APsFQQAg/AU3APwFQQAg/QU3AP0FQQAg/gU3AP4FQQAg/wU3AP8FQQAggAY3AIAGQQAggQY3AIEGQQAgggY3AIIGQQAggwY3AIMGQQAghAY3AIQGQQAghQY3AIUGQQAghgY3AIYGQQAghwY3AIcGQQAgiAY3AIgGQQAgiQY3AIkGQQAgigY3AIoGQQAgiwY3AIsGQQAgjAY3AIwGQQAgjQY3AI0GQQAgjgY3AI4GQQAgjwY3AI8GQQAgkAY3AJAGQQAgkQY3AJEGQQAgkgY3AJIGQQAgkwY3AJMGQQAglAY3AJQGQQAglQY3AJUGQQAglgY3AJYGQQAglwY3AJcGQQAgmAY3AJgGQQAgmQY3AJkGQQAgmgY3AJoGQQAgmwY3AJsGQQAgnAY3AJwGQQAgnQY3AJ0GQQAgngY3AJ4GQQAgnwY3AJ8GQQAgoAY3AKAGQQAgoQY3AKEGQQAgogY3AKIGQQAgowY3AKMGQQAgpAY3AKQGQQAgpQY3AKUGQQAgpgY3AKYGQQAgpwY3AKcGQQAgqAY3AKgGQQAgqQY3AKkGQQAgqgY3AKoGQQAgqwY3AKsGQQAgrAY3AKwGQQAgrQY3AK0GQQAgrgY3AK4GQQAgrwY3AK8GQQAgsAY3ALAGQQAgsQY3ALEGQQAgsgY3ALIGQQAgswY3ALMGQQAgtAY3ALQGQQAgtQY3ALUGQQAgtgY3ALYGQQAgtwY3ALcGQQAguAY3ALgGQQAguQY3ALkGQQAgugY3ALoGQQAguwY3ALsGQQAgvAY3ALwGQQAgvQY3AL0GQQAgvgY3AL4GQQAgvwY3AL8GQQAgwAY3AMAGQQAgwQY3AMEGQQAgwgY3AMIGQQAgwwY3AMMGQQAgxAY3AMQGQQAgxQY3AMUGQQAgxgY3AMYGQQAgxwY3AMcGQQAgyAY3AMgGQQAgyQY3AMkGQQAgygY3AMoGQQAgywY3AMsGQQAgzAY3AMwGQQAgzQY3AM0GQQAgzgY3AM4GQQAgzwY3AM8GQQAg0AY3ANAGQQAg0QY3ANEGQQAg0gY3ANIGQQAg0wY3ANMGQQAg1AY3ANQGQQAg1QY3ANUGQQAg1gY3ANYGQQAg1wY3ANcGQQAg2AY3ANgGQQAg2QY3ANkGQQAg2gY3ANoGQQAg2wY3ANsGQQAg3AY3ANwGQQAg3QY3AN0GQQAg3gY3AN4GQQAg3wY3AN8GQQAg4AY3AOAGQQAg4QY3AOEGQQAg4gY3AOIGQQAg4wY3AOMGQQAg5AY3AOQGQQAg5QY3AOUGQQAg5gY3AOYGQQAg5wY3AOcGQQAg6AY3AOgGQQAg6QY3AOkGQQAg6gY3AOoGQQAg6wY3AOsGQQAg7AY3AOwGQQAg7QY3AO0GQQAg7gY3AO4GQQAg7wY3AO8GQQAg8AY3APAGQQAg8QY3APEGQQAg8gY3APIGQQAg8wY3APMGQQAg9AY3APQGQQAg9QY3APUGQQAg9gY3APYGQQAg9wY3APcGQQAg+AY3APgGQQAg+QY3APkGQQAg+gY3APoGQQAg+wY3APsGQQAg/AY3APwGQQAg/QY3AP0GQQAg/gY3AP4GQQAg/wY3AP8GQQAggAc3AIAHQQAggQc3AIEHQQAgggc3AIIHQQAggwc3AIMHQQAghAc3AIQHQQAghQc3AIUHQQAghgc3AIYHQQAghwc3AIcHQQAgiAc3AIgHQQAgiQc3AIkHQQAgigc3AIoHQQAgiwc3AIsHQQAgjAc3AIwHQQAgjQc3AI0HQQAgjgc3AI4HQQAgjwc3AI8HQQAgkAc3AJAHQQAgkQc3AJEHQQAgkgc3AJIHQQAgkwc3AJMHQQAglAc3AJQHQQAglQc3AJUHQQAglgc3AJYHQQAglwc3AJcHQQAgmAc3AJgHQQAgmQc3AJkHQQAgmgc3AJoHQQAgmwc3AJsHQQAgnAc3AJwHQQAgnQc3AJ0HQQAgngc3AJ4HQQAgnwc3AJ8HQQAgoAc3AKAHQQAgoQc3AKEHQQAgogc3AKIHQQAgowc3AKMHQQAgpAc3AKQHQQAgpQc3AKUHQQAgpgc3AKYHQQAgpwc3AKcHQQAgqAc3AKgHQQAgqQc3AKkHQQAgqgc3AKoHQQAgqwc3AKsHQQAgrAc3AKwHQQAgrQc3AK0HQQAgrgc3AK4HQQAgrwc3AK8HQQAgsAc3ALAHQQAgsQc3ALEHQQAgsgc3ALIHQQAgswc3ALMHQQAgtAc3ALQHQQAgtQc3ALUHQQAgtgc3ALYHQQAgtwc3ALcHQQAguAc3ALgHQQAguQc3ALkHQQAgugc3ALoHQQAguwc3ALsHQQAgvAc3ALwHQQAgvQc3AL0HQQAgvgc3AL4HQQAgvwc3AL8HQQAgwAc3AMAHQQAgwQc3AMEHQQAgwgc3AMIHQQAgwwc3AMMHQQAgxAc3AMQHQQAgxQc3AMUHQQAgxgc3AMYHQQAgxwc3AMcHQQAgyAc3AMgHQQAgyQc3AMkHQQAgygc3AMoHQQAgywc3AMsHQQAgzAc3AMwHQQAgzQc3AM0HQQAgzgc3AM4HQQAgzwc3AM8HQQAg0Ac3ANAHQQAg0Qc3ANEHQQAg0gc3ANIHQQAg0wc3ANMHQQAg1Ac3ANQHQQAg1Qc3ANUHQQAg1gc3ANYHQQAg1wc3ANcHQQAg2Ac3ANgHQQAg2Qc3ANkHQQAg2gc3ANoHQQAg2wc3ANsHQQAg3Ac3ANwHQQAg3Qc3AN0HQQAg3gc3AN4HQQAg3wc3AN8HQQAg4Ac3AOAHQQAg4Qc3AOEHQQAg4gc3AOIHQQAg4wc3AOMHQQAg5Ac3AOQHQQAg5Qc3AOUHQQAg5gc3AOYHQQAg5wc3AOcHQQAg6Ac3AOgHQQAg6Qc3AOkHQQAg6gc3AOoHQQAg6wc3AOsHQQAg7Ac3AOwHQQAg7Qc3AO0HQQAg7gc3AO4HQQAg7wc3AO8HQQAg8Ac3APAHQQAg8Qc3APEHQQAg8gc3APIHQQAg8wc3APMHQQAg9Ac3APQHQQAg9Qc3APUHQQAg9gc3APYHQQAg9wc3APcHQQAg+Ac3APgHQQAg+Qc3APkHQQAg+gc3APoHQQAg+wc3APsHQQAg/Ac3APwHQQAg/Qc3AP0HQQAg/gc3AP4HQQAg/wc3AP8HQQAggAg3AIAIQQAggQg3AIEIQQAgggg3AIIIQQAggwg3AIMIQQAghAg3AIQIQQAghQg3AIUIQQAghgg3AIYIQQAghwg3AIcIQQAgiAg3AIgIQQAgiQg3AIkIQQAgigg3AIoIQQAgiwg3AIsIQQAgjAg3AIwIQQAgjQg3AI0IQQAgjgg3AI4IQQAgjwg3AI8IQQAgkAg3AJAIQQAgkQg3AJEIQQAgkgg3AJIIQQAgkwg3AJMIQQAglAg3AJQIQQAglQg3AJUIQQAglgg3AJYIQQAglwg3AJcIQQAgmAg3AJgIQQAgmQg3AJkIQQAgmgg3AJoIQQAgmwg3AJsIQQAgnAg3AJwIQQAgnQg3AJ0IQQAgngg3AJ4IQQAgnwg3AJ8ICw== +valid valid stack.wast:1 AGFzbQEAAAABDQNgAX4BfmAAAX9gAAADCQgAAAAAAAECAQYGAX8BQQALB1cGCGZhYy1leHByAAAJZmFjLXN0YWNrAAENZmFjLXN0YWNrLXJhdwACCWZhYy1taXhlZAADDWZhYy1taXhlZC1yYXcABBBub3QtcXVpdGUtYS10cmVlAAcKjwIILwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILDQEBf0EBIwBqJAAjAAsFABAFGgsJABAFEAUQBmoL +valid valid stack.wast:156 AGFzbQEAAAABDANgAABgAX8AYAABfwMCAQAEBAFwAAEKtAMBsQMAAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBAEEAEQEACwNAQQBBABEBAAtBAARAQQBBABEBAAtBAARAQQBBABEBAAVBAEEAEQEACwJ/QQARAgALGgN/QQARAgALGkEABH9BABECAAVBABECAAsaAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBAEEAEQEACwNAQQBBABEBAAtBAARAQQBBABEBAAtBAARAQQBBABEBAAVBAEEAEQEACwJ/QQARAgALGgN/QQARAgALGkEABH9BABECAAVBABECAAsaAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALQQARAAAL +invalid invalid start.wast:2 AGFzbQEAAAABBAFgAAADAgEACAEBCgQBAgAL +invalid invalid start.wast:7 AGFzbQEAAAABBQFgAAF/AwIBAAgBAAoHAQUAQQAPCw== +invalid invalid start.wast:14 AGFzbQEAAAABBQFgAX8AAwIBAAgBAAoEAQIACw== +valid valid start.wast:21 AGFzbQEAAAABCAJgAABgAAF/AwQDAAEABQQBAQEBBw0CA2luYwAAA2dldAABCAECCiMDDwBBAEEALQAAQQFqOgAACwgAQQAtAAAPCwgAEAAQABAACwsHAQBBAAsBQQ== +valid valid start.wast:80 AGFzbQEAAAABCAJgAX8AYAAAAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQgBAQoIAQYAQQEQAAs= +valid valid start.wast:86 AGFzbQEAAAABCAJgAX8AYAAAAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQgBAQoIAQYAQQIQAAs= +valid valid start.wast:92 AGFzbQEAAAABBAFgAAACEgEIc3BlY3Rlc3QFcHJpbnQAAAgBAA== +valid valid start.wast:98 AGFzbQEAAAABBAFgAAADAgEACAEACgUBAwAACw== +valid valid store.wast:3 AGFzbQEAAAABBAFgAAADCgkAAAAAAAAAAAAFAwEAAQeXAQkOYXMtYmxvY2stdmFsdWUAAA1hcy1sb29wLXZhbHVlAAELYXMtYnItdmFsdWUAAg5hcy1icl9pZi12YWx1ZQADE2FzLWJyX2lmLXZhbHVlLWNvbmQABBFhcy1icl90YWJsZS12YWx1ZQAFD2FzLXJldHVybi12YWx1ZQAGCmFzLWlmLXRoZW4ABwphcy1pZi1lbHNlAAgKiAEJDAACQEEAQQE2AgALCwwAA0BBAEEBNgIACwsOAAJAQQBBATYCAAwACwsQAAJAQQBBATYCAEEBDQALCxAAAkBBBkEAQQE2AgANAAsLEQACQEEAQQE2AgBBAQ4AAAsLCgBBAEEBNgIADwsOAEEBBEBBAEEBNgIACwsPAEEABEAFQQBBATYCAAsL +invalid invalid store.wast:112 AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAQoLAQkAQQBBATYCAAs= +invalid invalid store.wast:116 AGFzbQEAAAABBgFgAX4BfgMCAQAFAwEAAQoLAQkAQQBCATcDAAs= +invalid invalid store.wast:120 AGFzbQEAAAABBgFgAX0BfQMCAQAFAwEAAQoOAQwAQQBDAACAPzgCAAs= +invalid invalid store.wast:124 AGFzbQEAAAABBgFgAXwBfAMCAQAFAwEAAQoSARAAQQBEAAAAAAAA8D85AwAL +invalid invalid store.wast:128 AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAQoLAQkAQQBBAToAAAs= +invalid invalid store.wast:132 AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAQoLAQkAQQBBATsBAAs= +invalid invalid store.wast:136 AGFzbQEAAAABBgFgAX4BfgMCAQAFAwEAAQoLAQkAQQBCATwAAAs= +invalid invalid store.wast:140 AGFzbQEAAAABBgFgAX4BfgMCAQAFAwEAAQoLAQkAQQBCAT0BAAs= +invalid invalid store.wast:144 AGFzbQEAAAABBgFgAX4BfgMCAQAFAwEAAQoLAQkAQQBCAT4CAAs= +invalid invalid store.wast:150 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKBwEFADYCAAs= +invalid invalid store.wast:159 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCQEHAEEANgIACw== +invalid invalid store.wast:168 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAQQACQDYCAAsL +invalid invalid store.wast:178 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAAkBBADYCAAsL +invalid invalid store.wast:188 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAQQADQDYCAAsL +invalid invalid store.wast:198 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAA0BBADYCAAsL +invalid invalid store.wast:208 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAQQAEQDYCAAsL +invalid invalid store.wast:218 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEABEBBADYCAAsL +invalid invalid store.wast:228 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAEEAQQAEf0EABTYCAAsL +invalid invalid store.wast:238 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAEEABH9BAAVBADYCAAsL +invalid invalid store.wast:248 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEAEOAEEAQQACQDYCAAwACws= +invalid invalid store.wast:258 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEAEOAEEAAkBBADYCAAwACws= +invalid invalid store.wast:268 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEgEQAEEAQQACQDYCAEEBDQALCw== +invalid invalid store.wast:278 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEgEQAEEAAkBBADYCAEEBDQALCw== +invalid invalid store.wast:288 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAEEAQQACQDYCAA4AAAsL +invalid invalid store.wast:298 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAEEAAkBBADYCAA4AAAsL +invalid invalid store.wast:308 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCAEGADYCAA8L +invalid invalid store.wast:317 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCgEIAEEANgIADws= +invalid invalid store.wast:326 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDAEKADYCAEEBQQIbCw== +invalid invalid store.wast:335 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEANgIAQQFBAhsL +invalid invalid store.wast:344 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQMBAAEKDgIHADYCABABCwQAIAAL +invalid invalid store.wast:354 AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQMBAAEKEAIJAEEANgIAEAELBAAgAAs= +invalid invalid store.wast:364 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQUDAQABCQcBAEEACwEAChQCBAAgAAsNAAJ/NgIAQQARAAALCw== +invalid invalid store.wast:380 AGFzbQEAAAABCQJgAX8Bf2AAAAMDAgABBAUBcAEBAQUDAQABCQcBAEEACwEAChYCBAAgAAsPAAJ/QQA2AgBBABEAAAsL +invalid invalid store.wast:399 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEMAAAAAQQA2AgAL +invalid invalid store.wast:400 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEMAAAAAQQA6AAAL +invalid invalid store.wast:401 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEMAAAAAQQA7AQAL +invalid invalid store.wast:402 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEMAAAAAQQA3AwAL +invalid invalid store.wast:403 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEMAAAAAQgA8AAAL +invalid invalid store.wast:404 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEMAAAAAQgA9AQAL +invalid invalid store.wast:405 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEMAAAAAQgA+AgAL +invalid invalid store.wast:406 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEQEPAEMAAAAAQwAAAAA4AgAL +invalid invalid store.wast:407 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKFQETAEMAAAAARAAAAAAAAAAAOQMACw== +invalid invalid store.wast:409 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAQwAAAAA2AgAL +invalid invalid store.wast:410 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAQwAAAAA6AAAL +invalid invalid store.wast:411 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAQwAAAAA7AQAL +invalid invalid store.wast:412 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKDgEMAEEAQwAAAAA3AwAL +invalid invalid store.wast:413 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEgEQAEEARAAAAAAAAAAAPAAACw== +invalid invalid store.wast:414 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEgEQAEEARAAAAAAAAAAAPQEACw== +invalid invalid store.wast:415 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKEgEQAEEARAAAAAAAAAAAPgIACw== +invalid invalid store.wast:416 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCwEJAEEAQQA4AgAL +invalid invalid store.wast:417 AGFzbQEAAAABBAFgAAADAgEABQMBAAEKCwEJAEEAQgA5AwAL +valid valid switch.wast:1 AGFzbQEAAAABDwNgAX8Bf2ABfgF+YAABfwMFBAABAAIHHgQEc3RtdAAABGV4cHIAAQNhcmcAAgZjb3JuZXIAAwreAQRXAQF/QeQAIQECQAJAAkACQAJAAkACQAJAAkACQCAADggAAQIDBAUGCAcLIAAPCwELC0EAIABrIQEMBQsMBAtB5QAhAQwDC0HlACEBC0HmACEBCwsgAQ8LTAEBfkLkACEBAn4CQAJAAkACQAJAAkACQAJAAkAgAKcOCAABAgMGBQQIBwsgAA8LAQsLQgAgAH0MBQtC5QAhAQsLCyABDAELQnsLDwsqAAJ/QQoCf0HkAAJ/QegHAn9BAiAAbEEDIABxDgMBAgMAC2oLagtqCw8LDAACQEEADgAAC0EBCw== +invalid invalid switch.wast:150 AGFzbQEAAAABBAFgAAADAgEACgkBBwBBAA4AAws= +invalid invalid table-sub.wast:2 AGFzbQEAAAABBAFgAAADAgEABAcCcAAKbwAKCg4BDABBAEEBQQL8DgABCw== +invalid invalid table-sub.wast:13 AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQQBBW8ACg4BDABBAEEBQQL8DAAACw== +valid valid table.wast:3 AGFzbQEAAAAEBAFwAAA= +valid valid table.wast:4 AGFzbQEAAAAEBAFwAAE= +valid valid table.wast:5 AGFzbQEAAAAEBQFwAQAA +valid valid table.wast:6 AGFzbQEAAAAEBQFwAQAB +valid valid table.wast:7 AGFzbQEAAAAEBgFwAQGAAg== +valid valid table.wast:8 AGFzbQEAAAAEBwFwAQCAgAQ= +valid valid table.wast:9 AGFzbQEAAAAECQFwAQD/////Dw== +valid valid table.wast:11 AGFzbQEAAAAEBwJwAABwAAA= +valid valid table.wast:12 AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAABAQBcAAA +invalid invalid table.wast:19 AGFzbQEAAAAEBQFwAQEA +invalid invalid table.wast:23 AGFzbQEAAAAECQFwAf////8PAA== +valid valid table_copy.wast:6 AGFzbQEAAAABBQFgAAF/AwYFAAAAAAAHHwUDZWYwAAADZWYxAAEDZWYyAAIDZWYzAAMDZWY0AAQKGgUEAEEACwQAQQELBABBAgsEAEEDCwQAQQQL +valid valid table_copy.wast:15 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCi4IBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwMAAQsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:107 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BAkED/A4AAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:199 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBD0EC/A4AAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:291 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BGUED/A4AAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:383 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRRBFkEE/A4AAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:475 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBAUED/A4AAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:567 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBDEEH/A4AAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:659 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQxBCkEH/A4AAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:751 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBAEEU/A4BAAsHACAAEQAACwcAIAARAAEL +valid valid table_copy.wast:843 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCi4IBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwMAAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:935 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BAkED/A4BAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1027 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBD0EC/A4BAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1119 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BGUED/A4BAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1211 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRRBFkEE/A4BAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1303 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBAUED/A4BAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1395 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBDEEH/A4BAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1487 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQxBCkEH/A4BAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1579 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBAEEU/A4AAQsHACAAEQABCwcAIAARAAAL +valid valid table_copy.wast:1671 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wOAAAL +valid valid table_copy.wast:1696 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEF+QQFBAvwOAAAL +valid valid table_copy.wast:1721 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBBvwOAAAL +valid valid table_copy.wast:1746 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQX5BAvwOAAAL +valid valid table_copy.wast:1771 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBAPwOAAAL +valid valid table_copy.wast:1796 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQ9BAPwOAAAL +valid valid table_copy.wast:1821 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQ9BAPwOAAAL +valid valid table_copy.wast:1846 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR5BAPwOAAAL +valid valid table_copy.wast:1871 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR9BAPwOAAAL +valid valid table_copy.wast:1896 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQR5BAPwOAAAL +valid valid table_copy.wast:1921 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQR9BAPwOAAAL +valid valid table_copy.wast:1946 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wOAQAL +valid valid table_copy.wast:1971 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEF+QQFBAvwOAQAL +valid valid table_copy.wast:1996 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBBvwOAQAL +valid valid table_copy.wast:2021 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQX5BAvwOAQAL +valid valid table_copy.wast:2046 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBAPwOAQAL +valid valid table_copy.wast:2071 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQ9BAPwOAQAL +valid valid table_copy.wast:2096 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQ9BAPwOAQAL +valid valid table_copy.wast:2121 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR5BAPwOAQAL +valid valid table_copy.wast:2146 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR9BAPwOAQAL +valid valid table_copy.wast:2171 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQR5BAPwOAQAL +valid valid table_copy.wast:2196 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQR9BAPwOAQAL +valid valid table_copy.wast:2221 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEACwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL +valid valid table_copy.wast:2282 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ8BAEEACwkAAQIDBAUGBwgKZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw== +valid valid table_copy.wast:2343 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEYCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL +valid valid table_copy.wast:2404 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ8BAEEXCwkAAQIDBAUGBwgKZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw== +valid valid table_copy.wast:2465 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEELCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL +valid valid table_copy.wast:2587 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEVCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL +valid valid table_copy.wast:2709 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCREBAEEVCwsAAQIDBAUGBwgJCgpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL +valid valid table_copy.wast:2770 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBwFwAYABgAEHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFwEAQfAACxAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIAAgASAC/A4AAAs= +valid valid table_copy.wast:2927 AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBwFwAYABgAEHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFgEAQQALEAABAgMEBQYHCAkKCwwNDg8KZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw== +valid valid table_fill.wast:1 AGFzbQEAAAABDAJgA39vfwBgAX8BbwMEAwAAAQQEAW8ACgccAwRmaWxsAAALZmlsbC1hYmJyZXYAAQNnZXQAAgogAwsAIAAgASAC/BEACwsAIAAgASAC/BEACwYAIAAlAAs= +invalid invalid table_fill.wast:75 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCgcBBQD8EQAL +invalid invalid table_fill.wast:84 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCgsBCQDQb0EB/BEACw== +invalid invalid table_fill.wast:93 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCgsBCQBBAUEB/BEACw== +invalid invalid table_fill.wast:102 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCgsBCQBBAdBv/BEACw== +invalid invalid table_fill.wast:111 AGFzbQEAAAABBAFgAAADAgEABAQBbwAAChABDgBDAACAP9BvQQH8EQAL +invalid invalid table_fill.wast:120 AGFzbQEAAAABBQFgAW8AAwIBAAQEAXAAAAoNAQsAQQEgAEEB/BEACw== +invalid invalid table_fill.wast:129 AGFzbQEAAAABBAFgAAADAgEABAQBbwAAChABDgBBAdBvQwAAgD/8EQAL +invalid invalid table_fill.wast:139 AGFzbQEAAAABBQFgAW8AAwIBAAQHAm8AAXAAAQoNAQsAQQAgAEEB/BEBCw== +invalid invalid table_fill.wast:150 AGFzbQEAAAABBQFgAAF/AwIBAAQEAW8AAQoNAQsAQQDQb0EB/BEACw== +valid valid table_get.wast:1 AGFzbQEAAAABFwVgAABgAW8AYAF/AW9gAX8BcGABfwF/AwYFAAECAwQEBwJvAAJwAAMHOAQEaW5pdAABDWdldC1leHRlcm5yZWYAAgtnZXQtZnVuY3JlZgADD2lzX251bGwtZnVuY3JlZgAECQkBAgFBAQsAAQAKKwUCAAsQAEEBIAAmAEECQQElASYBCwYAIAAlAAsGACAAJQELBwAgABAD0Qs= +invalid invalid table_get.wast:42 AGFzbQEAAAABBQFgAAFvAwIBAAQEAW8ACgoGAQQAJQAL +invalid invalid table_get.wast:51 AGFzbQEAAAABBQFgAAFvAwIBAAQEAW8ACgoLAQkAQwAAgD8lAAs= +invalid invalid table_get.wast:61 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCggBBgBBACUACw== +invalid invalid table_get.wast:70 AGFzbQEAAAABBQFgAAFwAwIBAAQEAW8ACgoIAQYAQQElAAs= +invalid invalid table_get.wast:80 AGFzbQEAAAABBQFgAAFwAwIBAAQHAnAAAW8AAQoIAQYAQQAlAQs= +valid valid table_grow.wast:1 AGFzbQEAAAABFQRgAX8Bb2ACf28AYAJ/bwF/YAABfwMGBQABAgIDBAQBbwAABykFA2dldAAAA3NldAABBGdyb3cAAgtncm93LWFiYnJldgADBHNpemUABAorBQYAIAAlAAsIACAAIAEmAAsJACABIAD8DwALCQAgASAA/A8ACwUA/BAACw== +valid valid table_grow.wast:42 AGFzbQEAAAABBQFgAAF/AwIBAAQEAXAAEAcIAQRncm93AAAJBQEDAAEACgsBCQDSAEFw/A8ACw== +valid valid table_grow.wast:53 AGFzbQEAAAABBgFgAX8BfwMCAQAEBAFvAAAHCAEEZ3JvdwAACgsBCQDQbyAA/A8ACw== +valid valid table_grow.wast:67 AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFvAQAKBwgBBGdyb3cAAAoLAQkA0G8gAPwPAAs= +valid valid table_grow.wast:84 AGFzbQEAAAABDAJgAX8Bf2ACf38BcAMDAgABBAQBcAAKBxsCBGdyb3cAABBjaGVjay10YWJsZS1udWxsAAEJBQEDAAEBCj0CCQDQcCAA/A8ACzEBAXDSASECAkADQCAAJQAhAiAC0UUNASAAIAFPDQEgAEEBaiEAIAAgAU0NAAsLIAIL +valid valid table_grow.wast:111 AGFzbQEAAAABBQFgAAF/AwIBAAQEAXAAAQcQAgV0YWJsZQEABGdyb3cAAAoLAQkA0HBBAfwPAAs= +valid valid table_grow.wast:117 AGFzbQEAAAABBQFgAAF/AhcBC2dyb3duLXRhYmxlBXRhYmxlAXAAAgMCAQAHEAIFdGFibGUBAARncm93AAAKCwEJANBwQQH8DwAL +valid valid table_grow.wast:124 AGFzbQEAAAABBQFgAAF/AiABFGdyb3duLWltcG9ydGVkLXRhYmxlBXRhYmxlAXAAAwMCAQAHCAEEc2l6ZQAACgcBBQD8EAAL +invalid invalid table_grow.wast:135 AGFzbQEAAAABBQFgAAF/AwIBAAQEAW8AAAoHAQUA/A8ACw== +invalid invalid table_grow.wast:144 AGFzbQEAAAABBQFgAAF/AwIBAAQEAW8AAAoJAQcA0G/8DwAL +invalid invalid table_grow.wast:153 AGFzbQEAAAABBQFgAAF/AwIBAAQEAW8AAAoJAQcAQQH8DwAL +invalid invalid table_grow.wast:162 AGFzbQEAAAABBQFgAAF/AwIBAAQEAW8AAAoOAQwA0G9DAACAP/wPAAs= +invalid invalid table_grow.wast:171 AGFzbQEAAAABBgFgAW8BfwMCAQAEBAFwAAAKCwEJACAAQQH8DwAL +invalid invalid table_grow.wast:181 AGFzbQEAAAABBAFgAAADAgEABAQBbwABCgsBCQDQb0EA/A8ACw== +invalid invalid table_grow.wast:190 AGFzbQEAAAABBQFgAAF9AwIBAAQEAW8AAQoLAQkA0G9BAPwPAAs= +valid valid table_init.wast:15 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgovBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEHQQBBBPwMAQALBwAgABEAAAs= +valid valid table_init.wast:73 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgovBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQQFBA/wMAwALBwAgABEAAAs= +valid valid table_init.wast:131 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgpxBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQtOAEEHQQBBBPwMAQD8DQFBD0EBQQP8DAMA/A0DQRRBD0EF/A4AAEEVQR1BAfwOAABBGEEKQQH8DgAAQQ1BC0EE/A4AAEETQRRBBfwOAAALBwAgABEAAAs= +valid valid table_init.wast:197 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKLwcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBB0EAQQT8DAEBCwcAIAARAAEL +valid valid table_init.wast:255 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKLwcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBD0EBQQP8DAMBCwcAIAARAAEL +valid valid table_init.wast:313 AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKcQcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLTgBBB0EAQQT8DAEB/A0BQQ9BAUED/AwDAfwNA0EUQQ9BBfwOAQFBFUEdQQH8DgEBQRhBCkEB/A4BAUENQQtBBPwOAQFBE0EUQQX8DgEBCwcAIAARAAEL +invalid invalid table_init.wast:379 AGFzbQEAAAABBAFgAAADAgEABwgBBHRlc3QAAAoHAQUA/A0ACw== +invalid invalid table_init.wast:385 AGFzbQEAAAABBAFgAAADAgEABwgBBHRlc3QAAAoOAQwAQQxBAUEB/AwAAAs= +invalid invalid table_init.wast:391 AGFzbQEAAAABCAJgAAF/YAAAAwMCAAEHCAEEdGVzdAABCQUBAQABAAoMAgQAQQALBQD8DQQL +invalid invalid table_init.wast:399 AGFzbQEAAAABCAJgAAF/YAAAAwMCAAEHCAEEdGVzdAABCQUBAQABAAoTAgQAQQALDABBDEEBQQH8DAQACw== +valid valid table_init.wast:407 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCjkLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsFAPwNAgs= +valid valid table_init.wast:431 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQFBAfwMAgAL +valid valid table_init.wast:455 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkoLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsWAEEMQQFBAfwMAQBBFUEBQQH8DAEACw== +valid valid table_init.wast:479 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCjwLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsIAPwNAfwNAQs= +valid valid table_init.wast:503 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkMLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsPAPwNAUEMQQFBAfwMAQAL +valid valid table_init.wast:527 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQBBBfwMAQAL +valid valid table_init.wast:551 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQJBA/wMAQAL +valid valid table_init.wast:575 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wMAQAL +valid valid table_init.wast:599 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQRBAPwMAQAL +valid valid table_init.wast:623 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQVBAPwMAQAL +valid valid table_init.wast:647 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQJBAPwMAQAL +valid valid table_init.wast:671 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQJBAPwMAQAL +valid valid table_init.wast:695 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQRBAPwMAQAL +valid valid table_init.wast:719 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQVBAPwMAQAL +valid valid table_init.wast:743 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBGkEBQQP8DAEBCw== +valid valid table_init.wast:767 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBDEEEQQD8DAEBCw== +valid valid table_init.wast:791 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBDEEFQQD8DAEBCw== +valid valid table_init.wast:815 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHEECQQD8DAEBCw== +valid valid table_init.wast:839 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHUECQQD8DAEBCw== +valid valid table_init.wast:863 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHEEEQQD8DAEBCw== +valid valid table_init.wast:887 AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHUEFQQD8DAEBCw== +invalid invalid table_init.wast:912 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEEBQQFDAACAP/wMAAAL +invalid invalid table_init.wast:921 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKEQICAAsMAEEBQQFCAfwMAAAL +invalid invalid table_init.wast:930 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEEBQQFEAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:939 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEEBQwAAgD9BAfwMAAAL +invalid invalid table_init.wast:948 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFwICAAsSAEEBQwAAgD9DAACAP/wMAAAL +invalid invalid table_init.wast:957 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEEBQwAAgD9CAfwMAAAL +invalid invalid table_init.wast:966 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEEBQwAAgD9EAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:975 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKEQICAAsMAEEBQgFBAfwMAAAL +invalid invalid table_init.wast:984 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEEBQgFDAACAP/wMAAAL +invalid invalid table_init.wast:993 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKEQICAAsMAEEBQgFCAfwMAAAL +invalid invalid table_init.wast:1002 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEEBQgFEAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:1011 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEEBRAAAAAAAAPA/QQH8DAAACw== +invalid invalid table_init.wast:1020 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEEBRAAAAAAAAPA/QwAAgD/8DAAACw== +invalid invalid table_init.wast:1029 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEEBRAAAAAAAAPA/QgH8DAAACw== +invalid invalid table_init.wast:1038 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHwICAAsaAEEBRAAAAAAAAPA/RAAAAAAAAPA//AwAAAs= +invalid invalid table_init.wast:1047 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEMAAIA/QQFBAfwMAAAL +invalid invalid table_init.wast:1056 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFwICAAsSAEMAAIA/QQFDAACAP/wMAAAL +invalid invalid table_init.wast:1065 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEMAAIA/QQFCAfwMAAAL +invalid invalid table_init.wast:1074 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEMAAIA/QQFEAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:1083 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFwICAAsSAEMAAIA/QwAAgD9BAfwMAAAL +invalid invalid table_init.wast:1092 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGgICAAsVAEMAAIA/QwAAgD9DAACAP/wMAAAL +invalid invalid table_init.wast:1101 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFwICAAsSAEMAAIA/QwAAgD9CAfwMAAAL +invalid invalid table_init.wast:1110 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHgICAAsZAEMAAIA/QwAAgD9EAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:1119 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEMAAIA/QgFBAfwMAAAL +invalid invalid table_init.wast:1128 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFwICAAsSAEMAAIA/QgFDAACAP/wMAAAL +invalid invalid table_init.wast:1137 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEMAAIA/QgFCAfwMAAAL +invalid invalid table_init.wast:1146 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEMAAIA/QgFEAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:1155 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEMAAIA/RAAAAAAAAPA/QQH8DAAACw== +invalid invalid table_init.wast:1164 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHgICAAsZAEMAAIA/RAAAAAAAAPA/QwAAgD/8DAAACw== +invalid invalid table_init.wast:1173 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEMAAIA/RAAAAAAAAPA/QgH8DAAACw== +invalid invalid table_init.wast:1182 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKIgICAAsdAEMAAIA/RAAAAAAAAPA/RAAAAAAAAPA//AwAAAs= +invalid invalid table_init.wast:1191 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKEQICAAsMAEIBQQFBAfwMAAAL +invalid invalid table_init.wast:1200 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEIBQQFDAACAP/wMAAAL +invalid invalid table_init.wast:1209 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKEQICAAsMAEIBQQFCAfwMAAAL +invalid invalid table_init.wast:1218 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEIBQQFEAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:1227 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEIBQwAAgD9BAfwMAAAL +invalid invalid table_init.wast:1236 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFwICAAsSAEIBQwAAgD9DAACAP/wMAAAL +invalid invalid table_init.wast:1245 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEIBQwAAgD9CAfwMAAAL +invalid invalid table_init.wast:1254 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEIBQwAAgD9EAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:1263 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKEQICAAsMAEIBQgFBAfwMAAAL +invalid invalid table_init.wast:1272 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKFAICAAsPAEIBQgFDAACAP/wMAAAL +invalid invalid table_init.wast:1281 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKEQICAAsMAEIBQgFCAfwMAAAL +invalid invalid table_init.wast:1290 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEIBQgFEAAAAAAAA8D/8DAAACw== +invalid invalid table_init.wast:1299 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEIBRAAAAAAAAPA/QQH8DAAACw== +invalid invalid table_init.wast:1308 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEIBRAAAAAAAAPA/QwAAgD/8DAAACw== +invalid invalid table_init.wast:1317 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEIBRAAAAAAAAPA/QgH8DAAACw== +invalid invalid table_init.wast:1326 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHwICAAsaAEIBRAAAAAAAAPA/RAAAAAAAAPA//AwAAAs= +invalid invalid table_init.wast:1335 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEQAAAAAAADwP0EBQQH8DAAACw== +invalid invalid table_init.wast:1344 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEQAAAAAAADwP0EBQwAAgD/8DAAACw== +invalid invalid table_init.wast:1353 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEQAAAAAAADwP0EBQgH8DAAACw== +invalid invalid table_init.wast:1362 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHwICAAsaAEQAAAAAAADwP0EBRAAAAAAAAPA//AwAAAs= +invalid invalid table_init.wast:1371 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEQAAAAAAADwP0MAAIA/QQH8DAAACw== +invalid invalid table_init.wast:1380 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHgICAAsZAEQAAAAAAADwP0MAAIA/QwAAgD/8DAAACw== +invalid invalid table_init.wast:1389 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEQAAAAAAADwP0MAAIA/QgH8DAAACw== +invalid invalid table_init.wast:1398 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKIgICAAsdAEQAAAAAAADwP0MAAIA/RAAAAAAAAPA//AwAAAs= +invalid invalid table_init.wast:1407 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEQAAAAAAADwP0IBQQH8DAAACw== +invalid invalid table_init.wast:1416 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGwICAAsWAEQAAAAAAADwP0IBQwAAgD/8DAAACw== +invalid invalid table_init.wast:1425 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKGAICAAsTAEQAAAAAAADwP0IBQgH8DAAACw== +invalid invalid table_init.wast:1434 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHwICAAsaAEQAAAAAAADwP0IBRAAAAAAAAPA//AwAAAs= +invalid invalid table_init.wast:1443 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHwICAAsaAEQAAAAAAADwP0QAAAAAAADwP0EB/AwAAAs= +invalid invalid table_init.wast:1452 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKIgICAAsdAEQAAAAAAADwP0QAAAAAAADwP0MAAIA//AwAAAs= +invalid invalid table_init.wast:1461 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKHwICAAsaAEQAAAAAAADwP0QAAAAAAADwP0IB/AwAAAs= +invalid invalid table_init.wast:1470 AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQR0ZXN0AAEJBwEBAAMAAAAKJgICAAshAEQAAAAAAADwP0QAAAAAAADwP0QAAAAAAADwP/wMAAAL +valid valid table_init.wast:1478 AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABIEAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBACAB/AwAAAs= +valid valid table_init.wast:1602 AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQHAXABoAHAAgdkEgJmMAAAAmYxAAECZjIAAgJmMwADAmY0AAQCZjUABQJmNgAGAmY3AAcCZjgACAJmOQAJA2YxMAAKA2YxMQALA2YxMgAMA2YxMwANA2YxNAAOA2YxNQAPBHRlc3QAEANydW4AEQkUAQEAEAABAgMEBQYHCAkKCwwNDg8KZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgAEEAIAH8DAAACw== +valid valid table_init.wast:1982 AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABQEAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBACAB/AwAAAs= +valid valid table_init.wast:2076 AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABEBAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBCCAB/AwAAAs= +valid valid table_init.wast:2122 AGFzbQEAAAABBAFgAAADAgEABAQBcAABCcQBQQEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAoOAQwAQQBBAEEA/AxAAAs= +valid valid table_set.wast:1 AGFzbQEAAAABIgdgAABgAX8Bb2ABfwFwYAJ/bwBgAn9wAGACf38AYAF/AX8DCAcAAQIDBAUGBAcCbwABcAACB2IGDWdldC1leHRlcm5yZWYAAQtnZXQtZnVuY3JlZgACDXNldC1leHRlcm5yZWYAAwtzZXQtZnVuY3JlZgAEEHNldC1mdW5jcmVmLWZyb20ABQ9pc19udWxsLWZ1bmNyZWYABgkJAQIBQQELAAEACjcHAgALBgAgACUACwYAIAAlAQsIACAAIAEmAAsIACAAIAEmAQsKACAAIAElASYBCwcAIAAQAtEL +invalid invalid table_set.wast:55 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCgYBBAAmAAs= +invalid invalid table_set.wast:64 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCggBBgDQbyYACw== +invalid invalid table_set.wast:73 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCggBBgBBASYACw== +invalid invalid table_set.wast:82 AGFzbQEAAAABBAFgAAADAgEABAQBbwAKCg0BCwBDAACAP9BvJgAL +invalid invalid table_set.wast:91 AGFzbQEAAAABBQFgAW8AAwIBAAQEAXAACgoKAQgAQQEgACYACw== +invalid invalid table_set.wast:101 AGFzbQEAAAABBQFgAW8AAwIBAAQHAm8AAXAAAQoKAQgAQQAgACYBCw== +invalid invalid table_set.wast:112 AGFzbQEAAAABBQFgAAF/AwIBAAQEAW8ACgoKAQgAQQDQbyYACw== +valid valid table_size.wast:1 AGFzbQEAAAABCQJgAAF/YAF/AAMJCAAAAAABAQEBBA8EbwAAbwABbwEAAm8BAwgHUQgHc2l6ZS10MAAAB3NpemUtdDEAAQdzaXplLXQyAAIHc2l6ZS10MwADB2dyb3ctdDAABAdncm93LXQxAAUHZ3Jvdy10MgAGB2dyb3ctdDMABwpFCAUA/BAACwUA/BABCwUA/BACCwUA/BADCwoA0G8gAPwPABoLCgDQbyAA/A8BGgsKANBvIAD8DwIaCwoA0G8gAPwPAxoL +invalid invalid table_size.wast:70 AGFzbQEAAAABBAFgAAADAgEABAQBbwABCgcBBQD8EAAL +invalid invalid table_size.wast:79 AGFzbQEAAAABBQFgAAF9AwIBAAQEAW8AAQoHAQUA/BAACw== +valid valid token.wast:15 AGFzbQEAAAABBAFgAAADAgEACgUBAwABCw== +valid valid token.wast:18 AGFzbQEAAAABBAFgAAADAgEACgYBBAABAQs= +valid valid token.wast:30 AGFzbQEAAAABBAFgAAADAgEACgcBBQAMAAEL +valid valid token.wast:33 AGFzbQEAAAABBAFgAAADAgEABAQBcAABCQcBAEEACwEACgQBAgAL +valid valid token.wast:47 AGFzbQEAAAABBAFgAAACEgEIc3BlY3Rlc3QFcHJpbnQAAA== +valid valid token.wast:54 AGFzbQEAAAABBAFgAAADAgEACgQBAgAL +valid valid token.wast:70 AGFzbQEAAAABBAFgAAADAgEACgYBBAAMAAs= +valid valid token.wast:74 AGFzbQEAAAALBAEBAWE= +valid valid token.wast:82 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEADgEAAAsL +valid valid token.wast:112 AGFzbQEAAAABBAFgAAADAgEACgwBCgACQEEADgAACws= +valid valid token.wast:142 AGFzbQEAAAALBQEBAiBh +valid valid token.wast:152 AGFzbQEAAAALBQEBAmEg +valid valid token.wast:162 AGFzbQEAAAALBgEBA2EgYg== +valid valid token.wast:172 AGFzbQEAAAALCQEBBu+Ymu+SqQ== +valid valid token.wast:182 AGFzbQEAAAALCgEBByDvmJrvkqk= +valid valid token.wast:192 AGFzbQEAAAALCgEBB++Ymu+SqSA= +valid valid token.wast:202 AGFzbQEAAAALBQEBAmFi +valid valid token.wast:232 AGFzbQEAAAALDwEBDO+Ymu+Sqe+Ymu+SqQ== +valid valid token.wast:242 AGFzbQEAAAALEAEBDe+Ymu+SqSDvmJrvkqk= +valid valid traps.wast:5 AGFzbQEAAAABCwJgAn9/AGACfn4AAwUEAAABAQdNBBBub19kY2UuaTMyLmRpdl9zAAAQbm9fZGNlLmkzMi5kaXZfdQABEG5vX2RjZS5pNjQuZGl2X3MAAhBub19kY2UuaTY0LmRpdl91AAMKJQQIACAAIAFtGgsIACAAIAFuGgsIACAAIAF/GgsIACAAIAGAGgs= +valid valid traps.wast:23 AGFzbQEAAAABCwJgAn9/AGACfn4AAwUEAAABAQdNBBBub19kY2UuaTMyLnJlbV9zAAAQbm9fZGNlLmkzMi5yZW1fdQABEG5vX2RjZS5pNjQucmVtX3MAAhBub19kY2UuaTY0LnJlbV91AAMKJQQIACAAIAFvGgsIACAAIAFwGgsIACAAIAGBGgsIACAAIAGCGgs= +valid valid traps.wast:39 AGFzbQEAAAABCQJgAX0AYAF8AAMJCAAAAQEAAAEBB8kBCBZub19kY2UuaTMyLnRydW5jX2YzMl9zAAAWbm9fZGNlLmkzMi50cnVuY19mMzJfdQABFm5vX2RjZS5pMzIudHJ1bmNfZjY0X3MAAhZub19kY2UuaTMyLnRydW5jX2Y2NF91AAMWbm9fZGNlLmk2NC50cnVuY19mMzJfcwAEFm5vX2RjZS5pNjQudHJ1bmNfZjMyX3UABRZub19kY2UuaTY0LnRydW5jX2Y2NF9zAAYWbm9fZGNlLmk2NC50cnVuY19mNjRfdQAHCjkIBgAgAKgaCwYAIACpGgsGACAAqhoLBgAgAKsaCwYAIACuGgsGACAArxoLBgAgALAaCwYAIACxGgs= +valid valid traps.wast:59 AGFzbQEAAAABBQFgAX8AAw8OAAAAAAAAAAAAAAAAAAAFAwEAAQehAg4Pbm9fZGNlLmkzMi5sb2FkAAATbm9fZGNlLmkzMi5sb2FkMTZfcwABE25vX2RjZS5pMzIubG9hZDE2X3UAAhJub19kY2UuaTMyLmxvYWQ4X3MAAxJub19kY2UuaTMyLmxvYWQ4X3UABA9ub19kY2UuaTY0LmxvYWQABRNub19kY2UuaTY0LmxvYWQzMl9zAAYTbm9fZGNlLmk2NC5sb2FkMzJfdQAHE25vX2RjZS5pNjQubG9hZDE2X3MACBNub19kY2UuaTY0LmxvYWQxNl91AAkSbm9fZGNlLmk2NC5sb2FkOF9zAAoSbm9fZGNlLmk2NC5sb2FkOF91AAsPbm9fZGNlLmYzMi5sb2FkAAwPbm9fZGNlLmY2NC5sb2FkAA0Kfw4IACAAKAIAGgsIACAALgEAGgsIACAALwEAGgsIACAALAAAGgsIACAALQAAGgsIACAAKQMAGgsIACAANAIAGgsIACAANQIAGgsIACAAMgEAGgsIACAAMwEAGgsIACAAMAAAGgsIACAAMQAAGgsIACAAKgIAGgsIACAAKwMAGgs= +valid valid type.wast:3 AGFzbQEAAAABiQEXYAAAYAAAYAF/AGABfwBgAAF/YAF/AX9gAX8Bf2ACfXwAYAACfn1gAn9+An18YAJ9fABgAn18AGACfXwAYAJ9fABgAAJ+fWACf34CfXxgAn9+An18YAZ9fH98f38AYAAFfn59fX9gBH9/fn8EfXx8f2ADfXx/AGAAA35+fWAFf39+f38EfXx8fw== +valid valid unreachable.wast:3 AGFzbQEAAAABIAdgA39/fwBgAABgAAF/YAABfmAAAX1gAAF8YAJ/fwF/Az08AQACAwQFAgIBAgICAQICAgIBAgIBAgIBAgICAgMCBgYGBgYCAQEBAQEBAQEEBAQDAQEBAQQCAwICAgICBAUBcAEBAQUDAQABBgkBfQFDAAAAAAsHjAg6CHR5cGUtaTMyAAIIdHlwZS1pNjQAAwh0eXBlLWYzMgAECHR5cGUtZjY0AAUNYXMtZnVuYy1maXJzdAAGC2FzLWZ1bmMtbWlkAAcMYXMtZnVuYy1sYXN0AAgNYXMtZnVuYy12YWx1ZQAJDmFzLWJsb2NrLWZpcnN0AAoMYXMtYmxvY2stbWlkAAsNYXMtYmxvY2stbGFzdAAMDmFzLWJsb2NrLXZhbHVlAA0OYXMtYmxvY2stYnJva2UADg1hcy1sb29wLWZpcnN0AA8LYXMtbG9vcC1taWQAEAxhcy1sb29wLWxhc3QAEQ1hcy1sb29wLWJyb2tlABILYXMtYnItdmFsdWUAEw1hcy1icl9pZi1jb25kABQOYXMtYnJfaWYtdmFsdWUAFRNhcy1icl9pZi12YWx1ZS1jb25kABYRYXMtYnJfdGFibGUtaW5kZXgAFxFhcy1icl90YWJsZS12YWx1ZQAYE2FzLWJyX3RhYmxlLXZhbHVlLTIAGRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAaG2FzLWJyX3RhYmxlLXZhbHVlLWFuZC1pbmRleAAbD2FzLXJldHVybi12YWx1ZQAcCmFzLWlmLWNvbmQAHQphcy1pZi10aGVuAB4KYXMtaWYtZWxzZQAfEmFzLWlmLXRoZW4tbm8tZWxzZQAgD2FzLXNlbGVjdC1maXJzdAAhEGFzLXNlbGVjdC1zZWNvbmQAIg5hcy1zZWxlY3QtY29uZAAjDWFzLWNhbGwtZmlyc3QAJAthcy1jYWxsLW1pZAAlDGFzLWNhbGwtbGFzdAAmFWFzLWNhbGxfaW5kaXJlY3QtZnVuYwAnFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAKBRhcy1jYWxsX2luZGlyZWN0LW1pZAApFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAqEmFzLWxvY2FsLnNldC12YWx1ZQArEmFzLWxvY2FsLnRlZS12YWx1ZQAsE2FzLWdsb2JhbC5zZXQtdmFsdWUALQ9hcy1sb2FkLWFkZHJlc3MALhBhcy1sb2FkTi1hZGRyZXNzAC8QYXMtc3RvcmUtYWRkcmVzcwAwDmFzLXN0b3JlLXZhbHVlADERYXMtc3RvcmVOLWFkZHJlc3MAMg9hcy1zdG9yZU4tdmFsdWUAMxBhcy11bmFyeS1vcGVyYW5kADQOYXMtYmluYXJ5LWxlZnQANQ9hcy1iaW5hcnktcmlnaHQANg9hcy10ZXN0LW9wZXJhbmQANw9hcy1jb21wYXJlLWxlZnQAOBBhcy1jb21wYXJlLXJpZ2h0ADkSYXMtY29udmVydC1vcGVyYW5kADoTYXMtbWVtb3J5Lmdyb3ctc2l6ZQA7CQcBAEEACwEBCrUEPAIACwIACwMAAAsDAAALAwAACwMAAAsFAABBfwsHABAAAEF/CwUAEAAACwUAEAAACwgAAn8AQQILCwoAAn8QAABBAgsLCQACQAEQAAALCwkAAn8BEAAACwsMAAJ/EABBAQwAAAsLCAADfwBBAgsLCgADfxAAAEECCwsJAANAARAAAAsLDwACfwN/EABBAQwBAAsLCwgAAn8ADAALCwgAAkAADQALCw0AAn8AQQENABpBBwsLDQACf0EGAA0AGkEHCwsLAAJAAA4CAAAACwsPAAJ/AEEBDgIAAABBBwsLDwACfwJ/AEEBDgEAAQsLCw4AAn9BBgAOAQAAQQcLCwwAAn8ADgEAAEEICwsEAAAPCwsAAAR/QQAFQQELCwsAIAAEfwAFIAELCwsAIAAEfyABBQALCwoAIAAEQAALIAELCAAAIAAgARsLCAAgAAAgARsLCABBAEEBABsLCQAAQQJBAxABCwkAQQEAQQMQAQsJAEEBQQIAEAELDAAAQQFBAkEDEQAACwwAQQAAQQJBAxEAAAsMAEEAQQEAQQMRAAALDABBAEEBQQIAEQAACwcBAX0AIQALBwEBfQAiAAsFAAAkAAsGAAAqAgALBgAAMAAACw8AAEQAAAAAAAAcQDkDAAsIAEECADcDAAsIAABBBzoAAAsIAEECAD0BAAsEAACMCwYAAEEKagsGAEIKAH0LBAAARQsNAABEAAAAAAAAJEBlCwkAQwAAIEEAXAsEAACnCwUAAEAACw== +invalid invalid unreached-invalid.wast:4 AGFzbQEAAAABBAFgAAADAgEACggBBgAAIAAaCw== +invalid invalid unreached-invalid.wast:8 AGFzbQEAAAABBAFgAAADAgEACggBBgAAIwAaCw== +invalid invalid unreached-invalid.wast:12 AGFzbQEAAAABBAFgAAADAgEACgcBBQAAEAEL +invalid invalid unreached-invalid.wast:16 AGFzbQEAAAABBAFgAAADAgEACgcBBQAADAEL +invalid invalid unreached-invalid.wast:21 AGFzbQEAAAABBAFgAAADAgEACgkBBwAAQQBQGgs= +invalid invalid unreached-invalid.wast:27 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAAEIAQQAbCw== +invalid invalid unreached-invalid.wast:33 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAEIAQQAbQQBBABsL +invalid invalid unreached-invalid.wast:42 AGFzbQEAAAABBAFgAAADAgEACgcBBQAAQQAL +invalid invalid unreached-invalid.wast:46 AGFzbQEAAAABBAFgAAADAgEACgYBBAAARQs= +invalid invalid unreached-invalid.wast:50 AGFzbQEAAAABBAFgAAADAgEACggBBgAAQQBqCw== +invalid invalid unreached-invalid.wast:56 AGFzbQEAAAABBAFgAAADAgEACgYBBAAAGws= +invalid invalid unreached-invalid.wast:60 AGFzbQEAAAABBAFgAAADAgEACggBBgAAQQAbCw== +invalid invalid unreached-invalid.wast:64 AGFzbQEAAAABBAFgAAADAgEACgoBCAAAQQBBABsL +invalid invalid unreached-invalid.wast:71 AGFzbQEAAAABBAFgAAADAgEACg8BDQACQAwAAkABRRoLCws= +invalid invalid unreached-invalid.wast:77 AGFzbQEAAAABBAFgAAADAgEAChABDgACQAwAQwAAgD9FGgsL +invalid invalid unreached-invalid.wast:83 AGFzbQEAAAABBAFgAAADAgEAChABDgACQAwAAkBBAVsaCwsL +invalid invalid unreached-invalid.wast:89 AGFzbQEAAAABBAFgAAADAgEAChIBEAACQAwAQQFDAAAAAFsaCws= +invalid invalid unreached-invalid.wast:95 AGFzbQEAAAABBAFgAAADAgEACgsBCQACQAwAQQELCw== +invalid invalid unreached-invalid.wast:101 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn9BAQwAQwAAAAALCw== +invalid invalid unreached-invalid.wast:107 AGFzbQEAAAABBAFgAAADAgEACg4BDAACQANADAFBAQsLCw== +invalid invalid unreached-invalid.wast:113 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AA39BAQwBQwAAAAALCw== +invalid invalid unreached-invalid.wast:119 AGFzbQEAAAABBAFgAAADAgEACggBBgAMAEEBCw== +invalid invalid unreached-invalid.wast:125 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAQQEMAEMAAAAACw== +invalid invalid unreached-invalid.wast:132 AGFzbQEAAAABBAFgAAADAgEACgsBCQAPAkABRRoLCw== +invalid invalid unreached-invalid.wast:138 AGFzbQEAAAABBAFgAAADAgEACgwBCgAPQwAAgD9FGgs= +invalid invalid unreached-invalid.wast:144 AGFzbQEAAAABBAFgAAADAgEACgwBCgAPAkBBAVsaCws= +invalid invalid unreached-invalid.wast:150 AGFzbQEAAAABBAFgAAADAgEACg4BDAAPQQFDAAAAAFsaCw== +invalid invalid unreached-invalid.wast:156 AGFzbQEAAAABBAFgAAADAgEACgoBCAACQA9BAQsL +invalid invalid unreached-invalid.wast:162 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAn9BAUEAD0MAAAAACws= +invalid invalid unreached-invalid.wast:168 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQANAD0EBCwsL +invalid invalid unreached-invalid.wast:174 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AA39BAQ9DAAAAAAsL +invalid invalid unreached-invalid.wast:180 AGFzbQEAAAABBAFgAAADAgEACgcBBQAPQQEL +invalid invalid unreached-invalid.wast:186 AGFzbQEAAAABBQFgAAF/AwIBAAoMAQoAQQEPQwAAAAAL +invalid invalid unreached-invalid.wast:193 AGFzbQEAAAABBAFgAAADAgEACgsBCQAAAkABRRoLCw== +invalid invalid unreached-invalid.wast:199 AGFzbQEAAAABBAFgAAADAgEACgsBCQAAA0ABRRoLCw== +invalid invalid unreached-invalid.wast:205 AGFzbQEAAAABBAFgAAADAgEACgoBCAAAA38BRQsL +invalid invalid unreached-invalid.wast:211 AGFzbQEAAAABBAFgAAADAgEACgwBCgAAQwAAgD9FGgs= +invalid invalid unreached-invalid.wast:217 AGFzbQEAAAABBAFgAAADAgEACgwBCgAAAkBBAVsaCws= +invalid invalid unreached-invalid.wast:223 AGFzbQEAAAABBAFgAAADAgEACg4BDAAAQQFDAAAAAFsaCw== +invalid invalid unreached-invalid.wast:229 AGFzbQEAAAABBAFgAAADAgEACgoBCAACQABBAQsL +invalid invalid unreached-invalid.wast:235 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAn9BAQBDAAAAAAsL +invalid invalid unreached-invalid.wast:241 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQANAAEEBCwsL +invalid invalid unreached-invalid.wast:247 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAA38AQwAAAAALCw== +invalid invalid unreached-invalid.wast:253 AGFzbQEAAAABBAFgAAADAgEACgcBBQAAQQEL +invalid invalid unreached-invalid.wast:259 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAAEMAAAAACw== +invalid invalid unreached-invalid.wast:265 AGFzbQEAAAABBAFgAAADAgEACg0BCwAAQQAEQAFFGgsL +invalid invalid unreached-invalid.wast:271 AGFzbQEAAAABBAFgAAADAgEACg8BDQAAQQAEQAEFAUUaCws= +invalid invalid unreached-invalid.wast:277 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAARAAAUBRRoLCw== +invalid invalid unreached-invalid.wast:284 AGFzbQEAAAABBAFgAAADAgEACg4BDAACQAALAkABRRoLCw== +invalid invalid unreached-invalid.wast:290 AGFzbQEAAAABBAFgAAADAgEACg8BDQACQAALQwAAgD9FGgs= +invalid invalid unreached-invalid.wast:296 AGFzbQEAAAABBAFgAAADAgEACg8BDQACQAALAkBBAVsaCws= +invalid invalid unreached-invalid.wast:302 AGFzbQEAAAABBAFgAAADAgEAChEBDwACQAALQQFDAAAAAFsaCw== +invalid invalid unreached-invalid.wast:308 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQAJAAAtBAQsL +invalid invalid unreached-invalid.wast:314 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAAn9BAQJAAAtDAAAAAAsL +invalid invalid unreached-invalid.wast:321 AGFzbQEAAAABBAFgAAADAgEAChABDgACQANAAkAAC0EBCwsL +invalid invalid unreached-invalid.wast:327 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AA38CQAALQwAAAAALCw== +invalid invalid unreached-invalid.wast:334 AGFzbQEAAAABBAFgAAADAgEACgoBCAACQAALQQEL +invalid invalid unreached-invalid.wast:340 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAAkAAC0MAAAAACw== +invalid invalid unreached-invalid.wast:348 AGFzbQEAAAABBAFgAAADAgEACg8BDQADQAwACwJAAUUaCws= +invalid invalid unreached-invalid.wast:354 AGFzbQEAAAABBAFgAAADAgEAChABDgADQAwAC0MAAIA/RRoL +invalid invalid unreached-invalid.wast:360 AGFzbQEAAAABBAFgAAADAgEAChABDgADQAwACwJAQQFbGgsL +invalid invalid unreached-invalid.wast:366 AGFzbQEAAAABBAFgAAADAgEAChIBEAADQAwAC0EBQwAAAABbGgs= +invalid invalid unreached-invalid.wast:372 AGFzbQEAAAABBAFgAAADAgEACg4BDAACQANADAALQQELCw== +invalid invalid unreached-invalid.wast:378 AGFzbQEAAAABBQFgAAF/AwIBAAoTAREAAn9BAQNADAALQwAAAAALCw== +invalid invalid unreached-invalid.wast:384 AGFzbQEAAAABBAFgAAADAgEAChEBDwACQANAA0AMAAtBAQsLCw== +invalid invalid unreached-invalid.wast:390 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AA38DQAwAC0MAAAAACws= +invalid invalid unreached-invalid.wast:396 AGFzbQEAAAABBAFgAAADAgEACgsBCQADQAwAC0EBCw== +invalid invalid unreached-invalid.wast:402 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAA0AMAAtDAAAAAAs= +invalid invalid unreached-invalid.wast:409 AGFzbQEAAAABBAFgAAADAgEACgwBCgBBAARAAUUaCws= +invalid invalid unreached-invalid.wast:415 AGFzbQEAAAABBAFgAAADAgEAChABDgBBAARAQwAAgD9FGgsL +invalid invalid unreached-invalid.wast:421 AGFzbQEAAAABBAFgAAADAgEACg0BCwBBAARAQQFbGgsL +invalid invalid unreached-invalid.wast:427 AGFzbQEAAAABBAFgAAADAgEAChIBEABBAARAQQFDAAAAAFsaCws= +invalid invalid unreached-invalid.wast:433 AGFzbQEAAAABBAFgAAADAgEACgsBCQBBAARAQQELCw== +invalid invalid unreached-invalid.wast:439 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAQQAEf0MAAAAACws= +invalid invalid unreached-invalid.wast:445 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAARAAkBBAQsLCw== +invalid invalid unreached-invalid.wast:451 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AQQAEfwJ/QwAAAAALCws= +invalid invalid unreached-invalid.wast:457 AGFzbQEAAAABBAFgAAADAgEACg4BDABBAARAA0BBAQsLCw== +invalid invalid unreached-invalid.wast:463 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AQQAEfwN/QwAAAAALCws= +invalid invalid unreached-invalid.wast:470 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AQQEPRAAAAAAAAPA/Dws= +invalid invalid unreached-invalid.wast:477 AGFzbQEAAAABBQFgAAF/AwIBAAoWARQAAn9BAQwARAAAAAAAAPA/DAALCw== +invalid invalid unreached-invalid.wast:484 AGFzbQEAAAABBAFgAAADAgEACg8BDQACQABDAAAAAA0ACws= +invalid invalid unreached-invalid.wast:490 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAn8CQABBAEEADQELCws= +invalid invalid unreached-invalid.wast:498 AGFzbQEAAAABBQFgAAF/AwIBAAoUARIAAn8CfQBBAEEADQELGkEACws= +invalid invalid unreached-invalid.wast:507 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn8AQQBBAA0AQQALCw== +invalid invalid unreached-invalid.wast:515 AGFzbQEAAAABBAFgAAADAgEAChABDgACQABDAACAPw4AAAsL +invalid invalid unreached-invalid.wast:521 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAAn8AQwAAAABBAQ4AAAsL +invalid invalid unreached-invalid.wast:527 AGFzbQEAAAABBAFgAAADAgEAChMBEQACQAJ9AEEBDgIAAQALGgsL +invalid invalid unreached-invalid.wast:540 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEDAkAACwsL +invalid invalid unreached-invalid.wast:546 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAAkACQAALCws= +invalid invalid unreached-invalid.wast:552 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAAn5CAAJAAAsLCw== +invalid invalid unreached-invalid.wast:558 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAkBBAwJAQgEACwtBCQs= +invalid invalid unreached-invalid.wast:565 AGFzbQEAAAABBAFgAAADAgEACg4BDAACQEEDAkAMAQsLCw== +invalid invalid unreached-invalid.wast:571 AGFzbQEAAAABBQFgAAF/AwIBAAoOAQwAAn8CQEEADAELCws= +invalid invalid unreached-invalid.wast:577 AGFzbQEAAAABBQFgAAF/AwIBAAoQAQ4AAn9CAAJAQQAMAQsLCw== +invalid invalid unreached-invalid.wast:584 AGFzbQEAAAABBAFgAAADAgEAChEBDwACQAJAQQMCQAwCCwsLCw== +invalid invalid unreached-invalid.wast:590 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAn8CQAJAQQAMAgsLCws= +invalid invalid unreached-invalid.wast:596 AGFzbQEAAAABBQFgAAF/AwIBAAoTAREAAn8CfkIAAkBBAAwCCwsLCw== +invalid invalid unreached-invalid.wast:604 AGFzbQEAAAABBQFgAAF/AwIBAAoSARAAAkBBAwJAQgEMAQsLQQkL +invalid invalid unreached-invalid.wast:611 AGFzbQEAAAABBAFgAAADAgEACg0BCwACQEEDAkAPCwsL +invalid invalid unreached-invalid.wast:617 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAAkACQEEADwsLCw== +invalid invalid unreached-invalid.wast:623 AGFzbQEAAAABBQFgAAF/AwIBAAoPAQ0AAn5CAAJAQQAPCwsL +invalid invalid unreached-invalid.wast:629 AGFzbQEAAAABBQFgAAF/AwIBAAoTAREAAkBBAwJAQgFBAA8LC0EJCw== +invalid invalid unreached-invalid.wast:637 AGFzbQEAAAABBAFgAAADAgEACg0BCwADQEEDAkAACwsL +invalid invalid unreached-invalid.wast:643 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAA0ACQAALCws= +invalid invalid unreached-invalid.wast:649 AGFzbQEAAAABBQFgAAF/AwIBAAoNAQsAA35CAAJAAAsLCw== +invalid invalid unreached-invalid.wast:656 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAA0ABDAALCw== +invalid invalid unreached-invalid.wast:662 AGFzbQEAAAABBQFgAAF/AwIBAAoLAQkAA0BBAAwACws= +invalid invalid unreached-invalid.wast:669 AGFzbQEAAAABBAFgAAADAgEACgkBBwEBfwAiAAs= +invalid invalid unreached-invalid.wast:676 AGFzbQEAAAABBQFgAAF/AwIBAAoRAQ8AAn8CQABBAA0BC0EACws= +invalid invalid unreached-invalid.wast:687 AGFzbQEAAAABBQFgAAF+AwIBAAoIAQYAAA0ArQs= +invalid invalid unreached-invalid.wast:699 AGFzbQEAAAABBAFgAAADAgEACg0BCwAAQQFCAUEBGxoL +invalid invalid unreached-invalid.wast:704 AGFzbQEAAAABBAFgAAADAgEACg0BCwAAQgFBAUEBGxoL +invalid invalid unreached-invalid.wast:710 AGFzbQEAAAABBAFgAAADAgEACg0BCwAAQQFBAUIBGxoL +invalid invalid unreached-invalid.wast:715 AGFzbQEAAAABBAFgAAADAgEACgsBCQAAQQFCARsaCw== +invalid invalid unreached-invalid.wast:720 AGFzbQEAAAABBAFgAAADAgEACgkBBwAAQgEbGgs= +invalid invalid unreached-invalid.wast:726 AGFzbQEAAAABBQFgAAF/AwIBAAoKAQgAAEIBQQEbCw== +invalid invalid unreached-invalid.wast:738 AGFzbQEAAAABBgFgAX8BbwMCAQAKFgEUAAJvAn/QbyAADgIAAQALGtBvCws= +valid valid unreached-valid.wast:1 AGFzbQEAAAABEQRgAX8Bf2AAAGAAAX9gAAF+AwgHAAABAgMBAQeXAQcQc2VsZWN0LXRyYXAtbGVmdAAAEXNlbGVjdC10cmFwLXJpZ2h0AAEQc2VsZWN0LXVucmVhY2hlZAACGXNlbGVjdF91bnJlYWNoZWRfcmVzdWx0XzEAAxlzZWxlY3RfdW5yZWFjaGVkX3Jlc3VsdF8yAAQPdW5yZWFjaGFibGUtbnVtAAUPdW5yZWFjaGFibGUtcmVmAAYKUgcIAABBACAAGwsIAEEAACAAGwsgAAAbAEEAGwBBAEEAGwBBAEEAQQAbAEMAAAAAQQAbAAsFAAAbagsJAABCAEEAG3wLBgAAG0UaCwYAABvRGgs= +valid valid unreached-valid.wast:49 AGFzbQEAAAABBAFgAAADAgEABw8BC21lZXQtYm90dG9tAAAKHQEbAAJ8An0AQQEOAgABAQsaRAAAAAAAAAAACxoL +valid valid unwind.wast:3 AGFzbQEAAAABCAJgAABgAAF/AzIxAAABAAEAAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQeUCjEaZnVuYy11bndpbmQtYnktdW5yZWFjaGFibGUAABFmdW5jLXVud2luZC1ieS1icgABF2Z1bmMtdW53aW5kLWJ5LWJyLXZhbHVlAAIUZnVuYy11bndpbmQtYnktYnJfaWYAAxpmdW5jLXVud2luZC1ieS1icl9pZi12YWx1ZQAEF2Z1bmMtdW53aW5kLWJ5LWJyX3RhYmxlAAUdZnVuYy11bndpbmQtYnktYnJfdGFibGUtdmFsdWUABhVmdW5jLXVud2luZC1ieS1yZXR1cm4ABxtibG9jay11bndpbmQtYnktdW5yZWFjaGFibGUACBJibG9jay11bndpbmQtYnktYnIACRhibG9jay11bndpbmQtYnktYnItdmFsdWUAChVibG9jay11bndpbmQtYnktYnJfaWYACxtibG9jay11bndpbmQtYnktYnJfaWYtdmFsdWUADBhibG9jay11bndpbmQtYnktYnJfdGFibGUADR5ibG9jay11bndpbmQtYnktYnJfdGFibGUtdmFsdWUADhZibG9jay11bndpbmQtYnktcmV0dXJuAA8iYmxvY2stbmVzdGVkLXVud2luZC1ieS11bnJlYWNoYWJsZQAQGWJsb2NrLW5lc3RlZC11bndpbmQtYnktYnIAER9ibG9jay1uZXN0ZWQtdW53aW5kLWJ5LWJyLXZhbHVlABIcYmxvY2stbmVzdGVkLXVud2luZC1ieS1icl9pZgATImJsb2NrLW5lc3RlZC11bndpbmQtYnktYnJfaWYtdmFsdWUAFB9ibG9jay1uZXN0ZWQtdW53aW5kLWJ5LWJyX3RhYmxlABUlYmxvY2stbmVzdGVkLXVud2luZC1ieS1icl90YWJsZS12YWx1ZQAWHWJsb2NrLW5lc3RlZC11bndpbmQtYnktcmV0dXJuABcXdW5hcnktYWZ0ZXItdW5yZWFjaGFibGUAGA51bmFyeS1hZnRlci1icgAZEXVuYXJ5LWFmdGVyLWJyX2lmABoUdW5hcnktYWZ0ZXItYnJfdGFibGUAGxJ1bmFyeS1hZnRlci1yZXR1cm4AHBhiaW5hcnktYWZ0ZXItdW5yZWFjaGFibGUAHQ9iaW5hcnktYWZ0ZXItYnIAHhJiaW5hcnktYWZ0ZXItYnJfaWYAHxViaW5hcnktYWZ0ZXItYnJfdGFibGUAIBNiaW5hcnktYWZ0ZXItcmV0dXJuACEYc2VsZWN0LWFmdGVyLXVucmVhY2hhYmxlACIPc2VsZWN0LWFmdGVyLWJyACMSc2VsZWN0LWFmdGVyLWJyX2lmACQVc2VsZWN0LWFmdGVyLWJyX3RhYmxlACUTc2VsZWN0LWFmdGVyLXJldHVybgAmHWJsb2NrLXZhbHVlLWFmdGVyLXVucmVhY2hhYmxlACcUYmxvY2stdmFsdWUtYWZ0ZXItYnIAKBdibG9jay12YWx1ZS1hZnRlci1icl9pZgApGmJsb2NrLXZhbHVlLWFmdGVyLWJyX3RhYmxlACoYYmxvY2stdmFsdWUtYWZ0ZXItcmV0dXJuACscbG9vcC12YWx1ZS1hZnRlci11bnJlYWNoYWJsZQAsE2xvb3AtdmFsdWUtYWZ0ZXItYnIALRZsb29wLXZhbHVlLWFmdGVyLWJyX2lmAC4ZbG9vcC12YWx1ZS1hZnRlci1icl90YWJsZQAvF2xvb3AtdmFsdWUtYWZ0ZXItcmV0dXJuADAKvAYxBwBBA0IBAAsIAEEDQgEMAAsKAEEDQgFBCQwACwwAQQNCAUEBDQAaGgsOAEEDQgFBCUEBDQAaGgsLAEEDQgFBAA4AAAsNAEEDQgFBCUEADgAACwkAQQNCAUEJDwsKAAJAQQNCAQALCw0AAkBBA0IBDAALQQkLDQACf0EDQgFBCQwACwsRAAJAQQNCAUEBDQAaGgtBCQsRAAJ/QQNCAUEJQQENABoaCwsQAAJAQQNCAUEADgAAC0EJCxAAAn9BA0IBQQlBAA4AAAsLDAACf0EDQgFBCQ8LCw0AAn9BAwJAQgEACwsLEQACQEEDAkBCAQwBCxoLQQkLEAACf0EDAkBCAUEJDAELCwsUAAJAQQMCQEIBQQENARoLGgtBCQsUAAJ/QQMCQEIBQQlBAQ0BGhoLCwsUAAJAQQMCQEIBQQEOAAELGgtBCQsTAAJ/QQMCQEIBQQlBAQ4AAQsLCw8AAn9BAwJAQgFBCQ8LCwsJAEMAAAAAAFALDwACf0MAAAAAQQkMAFALCw8AAn9CAEEJQQENABpQCwsTAAJ/QwAAAABBCUEADgEAAFALCwsAQwAAAABBCQ9QCxIAQwAAAABEAAAAAAAA8D8AUQsYAAJ/QwAAAABEAAAAAAAA8D9BCQwAUQsLEQACf0IAQgFBCUEBDQAaUQsLGwACf0MAAAAARAAAAAAAAPA/QQlBAA4AAFELCxQAQwAAAABEAAAAAAAA8D9BCQ9RCxQAQwAAAABEAAAAAAAA8D9CAAAbCxoAAn9DAAAAAEQAAAAAAADwP0IAQQkMABsLCxMAAn9BAEEBQQBBCUEBDQAaGwsLHQACf0MAAAAARAAAAAAAAPA/QgBBCUEADgAAGwsLFgBDAAAAAEQAAAAAAADwP0IBQQkPGwsLAAJ/QwAAAAAACwsOAAJ/QwAAAABBCQwACwsOAAJ/QQBBCUEBDQAaCwsSAAJ/QwAAAABBCUEADgEAAAsLDQACf0MAAAAAQQkPCwsLAAN/QwAAAAAACwsRAAJ/A39DAAAAAEEJDAELCwsRAAJ/A39BAEEJQQENARoLCwsVAAJ/A39DAAAAAEEJQQAOAQEBCwsLDQADf0MAAAAAQQkPCws= +malformed invalid utf8-custom-section-id.wast:7 AGFzbQEAAAAAAgGA +malformed invalid utf8-custom-section-id.wast:17 AGFzbQEAAAAAAgGP +malformed invalid utf8-custom-section-id.wast:27 AGFzbQEAAAAAAgGQ +malformed invalid utf8-custom-section-id.wast:37 AGFzbQEAAAAAAgGf +malformed invalid utf8-custom-section-id.wast:47 AGFzbQEAAAAAAgGg +malformed invalid utf8-custom-section-id.wast:57 AGFzbQEAAAAAAgG/ +malformed invalid utf8-custom-section-id.wast:69 AGFzbQEAAAAABAPCgIA= +malformed invalid utf8-custom-section-id.wast:79 AGFzbQEAAAAAAgHC +malformed invalid utf8-custom-section-id.wast:89 AGFzbQEAAAAAAwLCLg== +malformed invalid utf8-custom-section-id.wast:101 AGFzbQEAAAAAAwLAgA== +malformed invalid utf8-custom-section-id.wast:111 AGFzbQEAAAAAAwLAvw== +malformed invalid utf8-custom-section-id.wast:121 AGFzbQEAAAAAAwLBgA== +malformed invalid utf8-custom-section-id.wast:131 AGFzbQEAAAAAAwLBvw== +malformed invalid utf8-custom-section-id.wast:141 AGFzbQEAAAAAAwLCAA== +malformed invalid utf8-custom-section-id.wast:151 AGFzbQEAAAAAAwLCfw== +malformed invalid utf8-custom-section-id.wast:161 AGFzbQEAAAAAAwLCwA== +malformed invalid utf8-custom-section-id.wast:171 AGFzbQEAAAAAAwLC/Q== +malformed invalid utf8-custom-section-id.wast:181 AGFzbQEAAAAAAwLfAA== +malformed invalid utf8-custom-section-id.wast:191 AGFzbQEAAAAAAwLffw== +malformed invalid utf8-custom-section-id.wast:201 AGFzbQEAAAAAAwLfwA== +malformed invalid utf8-custom-section-id.wast:211 AGFzbQEAAAAAAwLf/Q== +malformed invalid utf8-custom-section-id.wast:223 AGFzbQEAAAAABQThgICA +malformed invalid utf8-custom-section-id.wast:233 AGFzbQEAAAAAAwLhgA== +malformed invalid utf8-custom-section-id.wast:243 AGFzbQEAAAAABAPhgC4= +malformed invalid utf8-custom-section-id.wast:253 AGFzbQEAAAAAAgHh +malformed invalid utf8-custom-section-id.wast:263 AGFzbQEAAAAAAwLhLg== +malformed invalid utf8-custom-section-id.wast:275 AGFzbQEAAAAABAPgAKA= +malformed invalid utf8-custom-section-id.wast:285 AGFzbQEAAAAABAPgf6A= +malformed invalid utf8-custom-section-id.wast:295 AGFzbQEAAAAABAPggIA= +malformed invalid utf8-custom-section-id.wast:305 AGFzbQEAAAAABAPggKA= +malformed invalid utf8-custom-section-id.wast:315 AGFzbQEAAAAABAPgn6A= +malformed invalid utf8-custom-section-id.wast:325 AGFzbQEAAAAABAPgn78= +malformed invalid utf8-custom-section-id.wast:335 AGFzbQEAAAAABAPgwKA= +malformed invalid utf8-custom-section-id.wast:345 AGFzbQEAAAAABAPg/aA= +malformed invalid utf8-custom-section-id.wast:355 AGFzbQEAAAAABAPhAIA= +malformed invalid utf8-custom-section-id.wast:365 AGFzbQEAAAAABAPhf4A= +malformed invalid utf8-custom-section-id.wast:375 AGFzbQEAAAAABAPhwIA= +malformed invalid utf8-custom-section-id.wast:385 AGFzbQEAAAAABAPh/YA= +malformed invalid utf8-custom-section-id.wast:395 AGFzbQEAAAAABAPsAIA= +malformed invalid utf8-custom-section-id.wast:405 AGFzbQEAAAAABAPsf4A= +malformed invalid utf8-custom-section-id.wast:415 AGFzbQEAAAAABAPswIA= +malformed invalid utf8-custom-section-id.wast:425 AGFzbQEAAAAABAPs/YA= +malformed invalid utf8-custom-section-id.wast:435 AGFzbQEAAAAABAPtAIA= +malformed invalid utf8-custom-section-id.wast:445 AGFzbQEAAAAABAPtf4A= +malformed invalid utf8-custom-section-id.wast:455 AGFzbQEAAAAABAPtoIA= +malformed invalid utf8-custom-section-id.wast:465 AGFzbQEAAAAABAPtoL8= +malformed invalid utf8-custom-section-id.wast:475 AGFzbQEAAAAABAPtv4A= +malformed invalid utf8-custom-section-id.wast:485 AGFzbQEAAAAABAPtv78= +malformed invalid utf8-custom-section-id.wast:495 AGFzbQEAAAAABAPtwIA= +malformed invalid utf8-custom-section-id.wast:505 AGFzbQEAAAAABAPt/YA= +malformed invalid utf8-custom-section-id.wast:515 AGFzbQEAAAAABAPuAIA= +malformed invalid utf8-custom-section-id.wast:525 AGFzbQEAAAAABAPuf4A= +malformed invalid utf8-custom-section-id.wast:535 AGFzbQEAAAAABAPuwIA= +malformed invalid utf8-custom-section-id.wast:545 AGFzbQEAAAAABAPu/YA= +malformed invalid utf8-custom-section-id.wast:555 AGFzbQEAAAAABAPvAIA= +malformed invalid utf8-custom-section-id.wast:565 AGFzbQEAAAAABAPvf4A= +malformed invalid utf8-custom-section-id.wast:575 AGFzbQEAAAAABAPvwIA= +malformed invalid utf8-custom-section-id.wast:585 AGFzbQEAAAAABAPv/YA= +malformed invalid utf8-custom-section-id.wast:597 AGFzbQEAAAAABAPgoAA= +malformed invalid utf8-custom-section-id.wast:607 AGFzbQEAAAAABAPgoH8= +malformed invalid utf8-custom-section-id.wast:617 AGFzbQEAAAAABAPgoMA= +malformed invalid utf8-custom-section-id.wast:627 AGFzbQEAAAAABAPgoP0= +malformed invalid utf8-custom-section-id.wast:637 AGFzbQEAAAAABAPhgAA= +malformed invalid utf8-custom-section-id.wast:647 AGFzbQEAAAAABAPhgH8= +malformed invalid utf8-custom-section-id.wast:657 AGFzbQEAAAAABAPhgMA= +malformed invalid utf8-custom-section-id.wast:667 AGFzbQEAAAAABAPhgP0= +malformed invalid utf8-custom-section-id.wast:677 AGFzbQEAAAAABAPsgAA= +malformed invalid utf8-custom-section-id.wast:687 AGFzbQEAAAAABAPsgH8= +malformed invalid utf8-custom-section-id.wast:697 AGFzbQEAAAAABAPsgMA= +malformed invalid utf8-custom-section-id.wast:707 AGFzbQEAAAAABAPsgP0= +malformed invalid utf8-custom-section-id.wast:717 AGFzbQEAAAAABAPtgAA= +malformed invalid utf8-custom-section-id.wast:727 AGFzbQEAAAAABAPtgH8= +malformed invalid utf8-custom-section-id.wast:737 AGFzbQEAAAAABAPtgMA= +malformed invalid utf8-custom-section-id.wast:747 AGFzbQEAAAAABAPtgP0= +malformed invalid utf8-custom-section-id.wast:757 AGFzbQEAAAAABAPugAA= +malformed invalid utf8-custom-section-id.wast:767 AGFzbQEAAAAABAPugH8= +malformed invalid utf8-custom-section-id.wast:777 AGFzbQEAAAAABAPugMA= +malformed invalid utf8-custom-section-id.wast:787 AGFzbQEAAAAABAPugP0= +malformed invalid utf8-custom-section-id.wast:797 AGFzbQEAAAAABAPvgAA= +malformed invalid utf8-custom-section-id.wast:807 AGFzbQEAAAAABAPvgH8= +malformed invalid utf8-custom-section-id.wast:817 AGFzbQEAAAAABAPvgMA= +malformed invalid utf8-custom-section-id.wast:827 AGFzbQEAAAAABAPvgP0= +malformed invalid utf8-custom-section-id.wast:839 AGFzbQEAAAAABgXxgICAgA== +malformed invalid utf8-custom-section-id.wast:849 AGFzbQEAAAAABAPxgIA= +malformed invalid utf8-custom-section-id.wast:859 AGFzbQEAAAAABQTxgIAj +malformed invalid utf8-custom-section-id.wast:869 AGFzbQEAAAAAAwLxgA== +malformed invalid utf8-custom-section-id.wast:879 AGFzbQEAAAAABAPxgCM= +malformed invalid utf8-custom-section-id.wast:889 AGFzbQEAAAAAAgHx +malformed invalid utf8-custom-section-id.wast:899 AGFzbQEAAAAAAwLxIw== +malformed invalid utf8-custom-section-id.wast:911 AGFzbQEAAAAABQTwAJCQ +malformed invalid utf8-custom-section-id.wast:921 AGFzbQEAAAAABQTwf5CQ +malformed invalid utf8-custom-section-id.wast:931 AGFzbQEAAAAABQTwgICA +malformed invalid utf8-custom-section-id.wast:941 AGFzbQEAAAAABQTwgJCQ +malformed invalid utf8-custom-section-id.wast:951 AGFzbQEAAAAABQTwj5CQ +malformed invalid utf8-custom-section-id.wast:961 AGFzbQEAAAAABQTwj7+/ +malformed invalid utf8-custom-section-id.wast:971 AGFzbQEAAAAABQTwwJCQ +malformed invalid utf8-custom-section-id.wast:981 AGFzbQEAAAAABQTw/ZCQ +malformed invalid utf8-custom-section-id.wast:991 AGFzbQEAAAAABQTxAICA +malformed invalid utf8-custom-section-id.wast:1001 AGFzbQEAAAAABQTxf4CA +malformed invalid utf8-custom-section-id.wast:1011 AGFzbQEAAAAABQTxwICA +malformed invalid utf8-custom-section-id.wast:1021 AGFzbQEAAAAABQTx/YCA +malformed invalid utf8-custom-section-id.wast:1031 AGFzbQEAAAAABQTzAICA +malformed invalid utf8-custom-section-id.wast:1041 AGFzbQEAAAAABQTzf4CA +malformed invalid utf8-custom-section-id.wast:1051 AGFzbQEAAAAABQTzwICA +malformed invalid utf8-custom-section-id.wast:1061 AGFzbQEAAAAABQTz/YCA +malformed invalid utf8-custom-section-id.wast:1071 AGFzbQEAAAAABQT0AICA +malformed invalid utf8-custom-section-id.wast:1081 AGFzbQEAAAAABQT0f4CA +malformed invalid utf8-custom-section-id.wast:1091 AGFzbQEAAAAABQT0kICA +malformed invalid utf8-custom-section-id.wast:1101 AGFzbQEAAAAABQT0v4CA +malformed invalid utf8-custom-section-id.wast:1111 AGFzbQEAAAAABQT0wICA +malformed invalid utf8-custom-section-id.wast:1121 AGFzbQEAAAAABQT0/YCA +malformed invalid utf8-custom-section-id.wast:1131 AGFzbQEAAAAABQT1gICA +malformed invalid utf8-custom-section-id.wast:1141 AGFzbQEAAAAABQT3gICA +malformed invalid utf8-custom-section-id.wast:1151 AGFzbQEAAAAABQT3v7+/ +malformed invalid utf8-custom-section-id.wast:1163 AGFzbQEAAAAABQTwkACQ +malformed invalid utf8-custom-section-id.wast:1173 AGFzbQEAAAAABQTwkH+Q +malformed invalid utf8-custom-section-id.wast:1183 AGFzbQEAAAAABQTwkMCQ +malformed invalid utf8-custom-section-id.wast:1193 AGFzbQEAAAAABQTwkP2Q +malformed invalid utf8-custom-section-id.wast:1203 AGFzbQEAAAAABQTxgACA +malformed invalid utf8-custom-section-id.wast:1213 AGFzbQEAAAAABQTxgH+A +malformed invalid utf8-custom-section-id.wast:1223 AGFzbQEAAAAABQTxgMCA +malformed invalid utf8-custom-section-id.wast:1233 AGFzbQEAAAAABQTxgP2A +malformed invalid utf8-custom-section-id.wast:1243 AGFzbQEAAAAABQTzgACA +malformed invalid utf8-custom-section-id.wast:1253 AGFzbQEAAAAABQTzgH+A +malformed invalid utf8-custom-section-id.wast:1263 AGFzbQEAAAAABQTzgMCA +malformed invalid utf8-custom-section-id.wast:1273 AGFzbQEAAAAABQTzgP2A +malformed invalid utf8-custom-section-id.wast:1283 AGFzbQEAAAAABQT0gACA +malformed invalid utf8-custom-section-id.wast:1293 AGFzbQEAAAAABQT0gH+A +malformed invalid utf8-custom-section-id.wast:1303 AGFzbQEAAAAABQT0gMCA +malformed invalid utf8-custom-section-id.wast:1313 AGFzbQEAAAAABQT0gP2A +malformed invalid utf8-custom-section-id.wast:1325 AGFzbQEAAAAABQTwkJAA +malformed invalid utf8-custom-section-id.wast:1335 AGFzbQEAAAAABQTwkJB/ +malformed invalid utf8-custom-section-id.wast:1345 AGFzbQEAAAAABQTwkJDA +malformed invalid utf8-custom-section-id.wast:1355 AGFzbQEAAAAABQTwkJD9 +malformed invalid utf8-custom-section-id.wast:1365 AGFzbQEAAAAABQTxgIAA +malformed invalid utf8-custom-section-id.wast:1375 AGFzbQEAAAAABQTxgIB/ +malformed invalid utf8-custom-section-id.wast:1385 AGFzbQEAAAAABQTxgIDA +malformed invalid utf8-custom-section-id.wast:1395 AGFzbQEAAAAABQTxgID9 +malformed invalid utf8-custom-section-id.wast:1405 AGFzbQEAAAAABQTzgIAA +malformed invalid utf8-custom-section-id.wast:1415 AGFzbQEAAAAABQTzgIB/ +malformed invalid utf8-custom-section-id.wast:1425 AGFzbQEAAAAABQTzgIDA +malformed invalid utf8-custom-section-id.wast:1435 AGFzbQEAAAAABQTzgID9 +malformed invalid utf8-custom-section-id.wast:1445 AGFzbQEAAAAABQT0gIAA +malformed invalid utf8-custom-section-id.wast:1455 AGFzbQEAAAAABQT0gIB/ +malformed invalid utf8-custom-section-id.wast:1465 AGFzbQEAAAAABQT0gIDA +malformed invalid utf8-custom-section-id.wast:1475 AGFzbQEAAAAABQT0gID9 +malformed invalid utf8-custom-section-id.wast:1487 AGFzbQEAAAAABwb4gICAgIA= +malformed invalid utf8-custom-section-id.wast:1497 AGFzbQEAAAAABQT4gICA +malformed invalid utf8-custom-section-id.wast:1507 AGFzbQEAAAAABgX4gICAIw== +malformed invalid utf8-custom-section-id.wast:1517 AGFzbQEAAAAABAP4gIA= +malformed invalid utf8-custom-section-id.wast:1527 AGFzbQEAAAAABQT4gIAj +malformed invalid utf8-custom-section-id.wast:1537 AGFzbQEAAAAAAwL4gA== +malformed invalid utf8-custom-section-id.wast:1547 AGFzbQEAAAAABAP4gCM= +malformed invalid utf8-custom-section-id.wast:1557 AGFzbQEAAAAAAgH4 +malformed invalid utf8-custom-section-id.wast:1567 AGFzbQEAAAAAAwL4Iw== +malformed invalid utf8-custom-section-id.wast:1579 AGFzbQEAAAAABgX4gICAgA== +malformed invalid utf8-custom-section-id.wast:1589 AGFzbQEAAAAABgX7v7+/vw== +malformed invalid utf8-custom-section-id.wast:1601 AGFzbQEAAAAACAf8gICAgICA +malformed invalid utf8-custom-section-id.wast:1611 AGFzbQEAAAAABgX8gICAgA== +malformed invalid utf8-custom-section-id.wast:1621 AGFzbQEAAAAABwb8gICAgCM= +malformed invalid utf8-custom-section-id.wast:1631 AGFzbQEAAAAABQT8gICA +malformed invalid utf8-custom-section-id.wast:1641 AGFzbQEAAAAABgX8gICAIw== +malformed invalid utf8-custom-section-id.wast:1651 AGFzbQEAAAAABAP8gIA= +malformed invalid utf8-custom-section-id.wast:1661 AGFzbQEAAAAABQT8gIAj +malformed invalid utf8-custom-section-id.wast:1671 AGFzbQEAAAAAAwL8gA== +malformed invalid utf8-custom-section-id.wast:1681 AGFzbQEAAAAABAP8gCM= +malformed invalid utf8-custom-section-id.wast:1691 AGFzbQEAAAAAAgH8 +malformed invalid utf8-custom-section-id.wast:1701 AGFzbQEAAAAAAwL8Iw== +malformed invalid utf8-custom-section-id.wast:1713 AGFzbQEAAAAABwb8gICAgIA= +malformed invalid utf8-custom-section-id.wast:1723 AGFzbQEAAAAABwb9v7+/v78= +malformed invalid utf8-custom-section-id.wast:1735 AGFzbQEAAAAAAgH+ +malformed invalid utf8-custom-section-id.wast:1745 AGFzbQEAAAAAAgH/ +malformed invalid utf8-custom-section-id.wast:1755 AGFzbQEAAAAAAwL+/w== +malformed invalid utf8-custom-section-id.wast:1765 AGFzbQEAAAAABQQAAP7/ +malformed invalid utf8-custom-section-id.wast:1775 AGFzbQEAAAAAAwL//g== +malformed invalid utf8-custom-section-id.wast:1785 AGFzbQEAAAAABQT//gAA +malformed invalid utf8-import-field.wast:7 AGFzbQEAAAACCwEBgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:22 AGFzbQEAAAACCwEBjwR0ZXN0A38A +malformed invalid utf8-import-field.wast:37 AGFzbQEAAAACCwEBkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:52 AGFzbQEAAAACCwEBnwR0ZXN0A38A +malformed invalid utf8-import-field.wast:67 AGFzbQEAAAACCwEBoAR0ZXN0A38A +malformed invalid utf8-import-field.wast:82 AGFzbQEAAAACCwEBvwR0ZXN0A38A +malformed invalid utf8-import-field.wast:99 AGFzbQEAAAACDQEDwoCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:114 AGFzbQEAAAACCwEBwgR0ZXN0A38A +malformed invalid utf8-import-field.wast:129 AGFzbQEAAAACDAECwi4EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:146 AGFzbQEAAAACDAECwIAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:161 AGFzbQEAAAACDAECwL8EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:176 AGFzbQEAAAACDAECwYAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:191 AGFzbQEAAAACDAECwb8EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:206 AGFzbQEAAAACDAECwgAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:221 AGFzbQEAAAACDAECwn8EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:236 AGFzbQEAAAACDAECwsAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:251 AGFzbQEAAAACDAECwv0EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:266 AGFzbQEAAAACDAEC3wAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:281 AGFzbQEAAAACDAEC338EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:296 AGFzbQEAAAACDAEC38AEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:311 AGFzbQEAAAACDAEC3/0EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:328 AGFzbQEAAAACDgEE4YCAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:343 AGFzbQEAAAACDAEC4YAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:358 AGFzbQEAAAACDQED4YAuBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:373 AGFzbQEAAAACCwEB4QR0ZXN0A38A +malformed invalid utf8-import-field.wast:388 AGFzbQEAAAACDAEC4S4EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:405 AGFzbQEAAAACDQED4ACgBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:420 AGFzbQEAAAACDQED4H+gBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:435 AGFzbQEAAAACDQED4ICABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:450 AGFzbQEAAAACDQED4ICgBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:465 AGFzbQEAAAACDQED4J+gBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:480 AGFzbQEAAAACDQED4J+/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:495 AGFzbQEAAAACDQED4MCgBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:510 AGFzbQEAAAACDQED4P2gBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:525 AGFzbQEAAAACDQED4QCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:540 AGFzbQEAAAACDQED4X+ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:555 AGFzbQEAAAACDQED4cCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:570 AGFzbQEAAAACDQED4f2ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:585 AGFzbQEAAAACDQED7ACABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:600 AGFzbQEAAAACDQED7H+ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:615 AGFzbQEAAAACDQED7MCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:630 AGFzbQEAAAACDQED7P2ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:645 AGFzbQEAAAACDQED7QCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:660 AGFzbQEAAAACDQED7X+ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:675 AGFzbQEAAAACDQED7aCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:690 AGFzbQEAAAACDQED7aC/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:705 AGFzbQEAAAACDQED7b+ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:720 AGFzbQEAAAACDQED7b+/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:735 AGFzbQEAAAACDQED7cCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:750 AGFzbQEAAAACDQED7f2ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:765 AGFzbQEAAAACDQED7gCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:780 AGFzbQEAAAACDQED7n+ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:795 AGFzbQEAAAACDQED7sCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:810 AGFzbQEAAAACDQED7v2ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:825 AGFzbQEAAAACDQED7wCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:840 AGFzbQEAAAACDQED73+ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:855 AGFzbQEAAAACDQED78CABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:870 AGFzbQEAAAACDQED7/2ABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:887 AGFzbQEAAAACDQED4KAABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:902 AGFzbQEAAAACDQED4KB/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:917 AGFzbQEAAAACDQED4KDABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:932 AGFzbQEAAAACDQED4KD9BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:947 AGFzbQEAAAACDQED4YAABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:962 AGFzbQEAAAACDQED4YB/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:977 AGFzbQEAAAACDQED4YDABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:992 AGFzbQEAAAACDQED4YD9BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1007 AGFzbQEAAAACDQED7IAABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1022 AGFzbQEAAAACDQED7IB/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1037 AGFzbQEAAAACDQED7IDABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1052 AGFzbQEAAAACDQED7ID9BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1067 AGFzbQEAAAACDQED7YAABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1082 AGFzbQEAAAACDQED7YB/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1097 AGFzbQEAAAACDQED7YDABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1112 AGFzbQEAAAACDQED7YD9BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1127 AGFzbQEAAAACDQED7oAABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1142 AGFzbQEAAAACDQED7oB/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1157 AGFzbQEAAAACDQED7oDABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1172 AGFzbQEAAAACDQED7oD9BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1187 AGFzbQEAAAACDQED74AABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1202 AGFzbQEAAAACDQED74B/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1217 AGFzbQEAAAACDQED74DABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1232 AGFzbQEAAAACDQED74D9BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1249 AGFzbQEAAAACDwEF8YCAgIAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:1264 AGFzbQEAAAACDQED8YCABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1279 AGFzbQEAAAACDgEE8YCAIwR0ZXN0A38A +malformed invalid utf8-import-field.wast:1294 AGFzbQEAAAACDAEC8YAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:1309 AGFzbQEAAAACDQED8YAjBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:1324 AGFzbQEAAAACCwEB8QR0ZXN0A38A +malformed invalid utf8-import-field.wast:1339 AGFzbQEAAAACDAEC8SMEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:1356 AGFzbQEAAAACDgEE8ACQkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1371 AGFzbQEAAAACDgEE8H+QkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1386 AGFzbQEAAAACDgEE8ICAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1401 AGFzbQEAAAACDgEE8ICQkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1416 AGFzbQEAAAACDgEE8I+QkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1431 AGFzbQEAAAACDgEE8I+/vwR0ZXN0A38A +malformed invalid utf8-import-field.wast:1446 AGFzbQEAAAACDgEE8MCQkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1461 AGFzbQEAAAACDgEE8P2QkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1476 AGFzbQEAAAACDgEE8QCAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1491 AGFzbQEAAAACDgEE8X+AgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1506 AGFzbQEAAAACDgEE8cCAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1521 AGFzbQEAAAACDgEE8f2AgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1536 AGFzbQEAAAACDgEE8wCAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1551 AGFzbQEAAAACDgEE83+AgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1566 AGFzbQEAAAACDgEE88CAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1581 AGFzbQEAAAACDgEE8/2AgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1596 AGFzbQEAAAACDgEE9ACAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1611 AGFzbQEAAAACDgEE9H+AgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1626 AGFzbQEAAAACDgEE9JCAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1641 AGFzbQEAAAACDgEE9L+AgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1656 AGFzbQEAAAACDgEE9MCAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1671 AGFzbQEAAAACDgEE9P2AgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1686 AGFzbQEAAAACDgEE9YCAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1701 AGFzbQEAAAACDgEE94CAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1716 AGFzbQEAAAACDgEE97+/vwR0ZXN0A38A +malformed invalid utf8-import-field.wast:1733 AGFzbQEAAAACDgEE8JAAkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1748 AGFzbQEAAAACDgEE8JB/kAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1763 AGFzbQEAAAACDgEE8JDAkAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1778 AGFzbQEAAAACDgEE8JD9kAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1793 AGFzbQEAAAACDgEE8YAAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1808 AGFzbQEAAAACDgEE8YB/gAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1823 AGFzbQEAAAACDgEE8YDAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1838 AGFzbQEAAAACDgEE8YD9gAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1853 AGFzbQEAAAACDgEE84AAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1868 AGFzbQEAAAACDgEE84B/gAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1883 AGFzbQEAAAACDgEE84DAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1898 AGFzbQEAAAACDgEE84D9gAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1913 AGFzbQEAAAACDgEE9IAAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1928 AGFzbQEAAAACDgEE9IB/gAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1943 AGFzbQEAAAACDgEE9IDAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1958 AGFzbQEAAAACDgEE9ID9gAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1975 AGFzbQEAAAACDgEE8JCQAAR0ZXN0A38A +malformed invalid utf8-import-field.wast:1990 AGFzbQEAAAACDgEE8JCQfwR0ZXN0A38A +malformed invalid utf8-import-field.wast:2005 AGFzbQEAAAACDgEE8JCQwAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2020 AGFzbQEAAAACDgEE8JCQ/QR0ZXN0A38A +malformed invalid utf8-import-field.wast:2035 AGFzbQEAAAACDgEE8YCAAAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2050 AGFzbQEAAAACDgEE8YCAfwR0ZXN0A38A +malformed invalid utf8-import-field.wast:2065 AGFzbQEAAAACDgEE8YCAwAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2080 AGFzbQEAAAACDgEE8YCA/QR0ZXN0A38A +malformed invalid utf8-import-field.wast:2095 AGFzbQEAAAACDgEE84CAAAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2110 AGFzbQEAAAACDgEE84CAfwR0ZXN0A38A +malformed invalid utf8-import-field.wast:2125 AGFzbQEAAAACDgEE84CAwAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2140 AGFzbQEAAAACDgEE84CA/QR0ZXN0A38A +malformed invalid utf8-import-field.wast:2155 AGFzbQEAAAACDgEE9ICAAAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2170 AGFzbQEAAAACDgEE9ICAfwR0ZXN0A38A +malformed invalid utf8-import-field.wast:2185 AGFzbQEAAAACDgEE9ICAwAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2200 AGFzbQEAAAACDgEE9ICA/QR0ZXN0A38A +malformed invalid utf8-import-field.wast:2217 AGFzbQEAAAACEAEG+ICAgICABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2232 AGFzbQEAAAACDgEE+ICAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2247 AGFzbQEAAAACDwEF+ICAgCMEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2262 AGFzbQEAAAACDQED+ICABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2277 AGFzbQEAAAACDgEE+ICAIwR0ZXN0A38A +malformed invalid utf8-import-field.wast:2292 AGFzbQEAAAACDAEC+IAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2307 AGFzbQEAAAACDQED+IAjBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2322 AGFzbQEAAAACCwEB+AR0ZXN0A38A +malformed invalid utf8-import-field.wast:2337 AGFzbQEAAAACDAEC+CMEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2354 AGFzbQEAAAACDwEF+ICAgIAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2369 AGFzbQEAAAACDwEF+7+/v78EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2386 AGFzbQEAAAACEQEH/ICAgICAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2401 AGFzbQEAAAACDwEF/ICAgIAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2416 AGFzbQEAAAACEAEG/ICAgIAjBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2431 AGFzbQEAAAACDgEE/ICAgAR0ZXN0A38A +malformed invalid utf8-import-field.wast:2446 AGFzbQEAAAACDwEF/ICAgCMEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2461 AGFzbQEAAAACDQED/ICABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2476 AGFzbQEAAAACDgEE/ICAIwR0ZXN0A38A +malformed invalid utf8-import-field.wast:2491 AGFzbQEAAAACDAEC/IAEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2506 AGFzbQEAAAACDQED/IAjBHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2521 AGFzbQEAAAACCwEB/AR0ZXN0A38A +malformed invalid utf8-import-field.wast:2536 AGFzbQEAAAACDAEC/CMEdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2553 AGFzbQEAAAACEAEG/ICAgICABHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2568 AGFzbQEAAAACEAEG/b+/v7+/BHRlc3QDfwA= +malformed invalid utf8-import-field.wast:2585 AGFzbQEAAAACCwEB/gR0ZXN0A38A +malformed invalid utf8-import-field.wast:2600 AGFzbQEAAAACCwEB/wR0ZXN0A38A +malformed invalid utf8-import-field.wast:2615 AGFzbQEAAAACDAEC/v8EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2630 AGFzbQEAAAACDgEEAAD+/wR0ZXN0A38A +malformed invalid utf8-import-field.wast:2645 AGFzbQEAAAACDAEC//4EdGVzdAN/AA== +malformed invalid utf8-import-field.wast:2660 AGFzbQEAAAACDgEE//4AAAR0ZXN0A38A +malformed invalid utf8-import-module.wast:7 AGFzbQEAAAACCwEEdGVzdAGAA38A +malformed invalid utf8-import-module.wast:22 AGFzbQEAAAACCwEEdGVzdAGPA38A +malformed invalid utf8-import-module.wast:37 AGFzbQEAAAACCwEEdGVzdAGQA38A +malformed invalid utf8-import-module.wast:52 AGFzbQEAAAACCwEEdGVzdAGfA38A +malformed invalid utf8-import-module.wast:67 AGFzbQEAAAACCwEEdGVzdAGgA38A +malformed invalid utf8-import-module.wast:82 AGFzbQEAAAACCwEEdGVzdAG/A38A +malformed invalid utf8-import-module.wast:99 AGFzbQEAAAACDQEEdGVzdAPCgIADfwA= +malformed invalid utf8-import-module.wast:114 AGFzbQEAAAACCwEEdGVzdAHCA38A +malformed invalid utf8-import-module.wast:129 AGFzbQEAAAACDAEEdGVzdALCLgN/AA== +malformed invalid utf8-import-module.wast:146 AGFzbQEAAAACDAEEdGVzdALAgAN/AA== +malformed invalid utf8-import-module.wast:161 AGFzbQEAAAACDAEEdGVzdALAvwN/AA== +malformed invalid utf8-import-module.wast:176 AGFzbQEAAAACDAEEdGVzdALBgAN/AA== +malformed invalid utf8-import-module.wast:191 AGFzbQEAAAACDAEEdGVzdALBvwN/AA== +malformed invalid utf8-import-module.wast:206 AGFzbQEAAAACDAEEdGVzdALCAAN/AA== +malformed invalid utf8-import-module.wast:221 AGFzbQEAAAACDAEEdGVzdALCfwN/AA== +malformed invalid utf8-import-module.wast:236 AGFzbQEAAAACDAEEdGVzdALCwAN/AA== +malformed invalid utf8-import-module.wast:251 AGFzbQEAAAACDAEEdGVzdALC/QN/AA== +malformed invalid utf8-import-module.wast:266 AGFzbQEAAAACDAEEdGVzdALfAAN/AA== +malformed invalid utf8-import-module.wast:281 AGFzbQEAAAACDAEEdGVzdALffwN/AA== +malformed invalid utf8-import-module.wast:296 AGFzbQEAAAACDAEEdGVzdALfwAN/AA== +malformed invalid utf8-import-module.wast:311 AGFzbQEAAAACDAEEdGVzdALf/QN/AA== +malformed invalid utf8-import-module.wast:328 AGFzbQEAAAACDgEEdGVzdAThgICAA38A +malformed invalid utf8-import-module.wast:343 AGFzbQEAAAACDAEEdGVzdALhgAN/AA== +malformed invalid utf8-import-module.wast:358 AGFzbQEAAAACDQEEdGVzdAPhgC4DfwA= +malformed invalid utf8-import-module.wast:373 AGFzbQEAAAACCwEEdGVzdAHhA38A +malformed invalid utf8-import-module.wast:388 AGFzbQEAAAACDAEEdGVzdALhLgN/AA== +malformed invalid utf8-import-module.wast:405 AGFzbQEAAAACDQEEdGVzdAPgAKADfwA= +malformed invalid utf8-import-module.wast:420 AGFzbQEAAAACDQEEdGVzdAPgf6ADfwA= +malformed invalid utf8-import-module.wast:435 AGFzbQEAAAACDQEEdGVzdAPggIADfwA= +malformed invalid utf8-import-module.wast:450 AGFzbQEAAAACDQEEdGVzdAPggKADfwA= +malformed invalid utf8-import-module.wast:465 AGFzbQEAAAACDQEEdGVzdAPgn6ADfwA= +malformed invalid utf8-import-module.wast:480 AGFzbQEAAAACDQEEdGVzdAPgn78DfwA= +malformed invalid utf8-import-module.wast:495 AGFzbQEAAAACDQEEdGVzdAPgwKADfwA= +malformed invalid utf8-import-module.wast:510 AGFzbQEAAAACDQEEdGVzdAPg/aADfwA= +malformed invalid utf8-import-module.wast:525 AGFzbQEAAAACDQEEdGVzdAPhAIADfwA= +malformed invalid utf8-import-module.wast:540 AGFzbQEAAAACDQEEdGVzdAPhf4ADfwA= +malformed invalid utf8-import-module.wast:555 AGFzbQEAAAACDQEEdGVzdAPhwIADfwA= +malformed invalid utf8-import-module.wast:570 AGFzbQEAAAACDQEEdGVzdAPh/YADfwA= +malformed invalid utf8-import-module.wast:585 AGFzbQEAAAACDQEEdGVzdAPsAIADfwA= +malformed invalid utf8-import-module.wast:600 AGFzbQEAAAACDQEEdGVzdAPsf4ADfwA= +malformed invalid utf8-import-module.wast:615 AGFzbQEAAAACDQEEdGVzdAPswIADfwA= +malformed invalid utf8-import-module.wast:630 AGFzbQEAAAACDQEEdGVzdAPs/YADfwA= +malformed invalid utf8-import-module.wast:645 AGFzbQEAAAACDQEEdGVzdAPtAIADfwA= +malformed invalid utf8-import-module.wast:660 AGFzbQEAAAACDQEEdGVzdAPtf4ADfwA= +malformed invalid utf8-import-module.wast:675 AGFzbQEAAAACDQEEdGVzdAPtoIADfwA= +malformed invalid utf8-import-module.wast:690 AGFzbQEAAAACDQEEdGVzdAPtoL8DfwA= +malformed invalid utf8-import-module.wast:705 AGFzbQEAAAACDQEEdGVzdAPtv4ADfwA= +malformed invalid utf8-import-module.wast:720 AGFzbQEAAAACDQEEdGVzdAPtv78DfwA= +malformed invalid utf8-import-module.wast:735 AGFzbQEAAAACDQEEdGVzdAPtwIADfwA= +malformed invalid utf8-import-module.wast:750 AGFzbQEAAAACDQEEdGVzdAPt/YADfwA= +malformed invalid utf8-import-module.wast:765 AGFzbQEAAAACDQEEdGVzdAPuAIADfwA= +malformed invalid utf8-import-module.wast:780 AGFzbQEAAAACDQEEdGVzdAPuf4ADfwA= +malformed invalid utf8-import-module.wast:795 AGFzbQEAAAACDQEEdGVzdAPuwIADfwA= +malformed invalid utf8-import-module.wast:810 AGFzbQEAAAACDQEEdGVzdAPu/YADfwA= +malformed invalid utf8-import-module.wast:825 AGFzbQEAAAACDQEEdGVzdAPvAIADfwA= +malformed invalid utf8-import-module.wast:840 AGFzbQEAAAACDQEEdGVzdAPvf4ADfwA= +malformed invalid utf8-import-module.wast:855 AGFzbQEAAAACDQEEdGVzdAPvwIADfwA= +malformed invalid utf8-import-module.wast:870 AGFzbQEAAAACDQEEdGVzdAPv/YADfwA= +malformed invalid utf8-import-module.wast:887 AGFzbQEAAAACDQEEdGVzdAPgoAADfwA= +malformed invalid utf8-import-module.wast:902 AGFzbQEAAAACDQEEdGVzdAPgoH8DfwA= +malformed invalid utf8-import-module.wast:917 AGFzbQEAAAACDQEEdGVzdAPgoMADfwA= +malformed invalid utf8-import-module.wast:932 AGFzbQEAAAACDQEEdGVzdAPgoP0DfwA= +malformed invalid utf8-import-module.wast:947 AGFzbQEAAAACDQEEdGVzdAPhgAADfwA= +malformed invalid utf8-import-module.wast:962 AGFzbQEAAAACDQEEdGVzdAPhgH8DfwA= +malformed invalid utf8-import-module.wast:977 AGFzbQEAAAACDQEEdGVzdAPhgMADfwA= +malformed invalid utf8-import-module.wast:992 AGFzbQEAAAACDQEEdGVzdAPhgP0DfwA= +malformed invalid utf8-import-module.wast:1007 AGFzbQEAAAACDQEEdGVzdAPsgAADfwA= +malformed invalid utf8-import-module.wast:1022 AGFzbQEAAAACDQEEdGVzdAPsgH8DfwA= +malformed invalid utf8-import-module.wast:1037 AGFzbQEAAAACDQEEdGVzdAPsgMADfwA= +malformed invalid utf8-import-module.wast:1052 AGFzbQEAAAACDQEEdGVzdAPsgP0DfwA= +malformed invalid utf8-import-module.wast:1067 AGFzbQEAAAACDQEEdGVzdAPtgAADfwA= +malformed invalid utf8-import-module.wast:1082 AGFzbQEAAAACDQEEdGVzdAPtgH8DfwA= +malformed invalid utf8-import-module.wast:1097 AGFzbQEAAAACDQEEdGVzdAPtgMADfwA= +malformed invalid utf8-import-module.wast:1112 AGFzbQEAAAACDQEEdGVzdAPtgP0DfwA= +malformed invalid utf8-import-module.wast:1127 AGFzbQEAAAACDQEEdGVzdAPugAADfwA= +malformed invalid utf8-import-module.wast:1142 AGFzbQEAAAACDQEEdGVzdAPugH8DfwA= +malformed invalid utf8-import-module.wast:1157 AGFzbQEAAAACDQEEdGVzdAPugMADfwA= +malformed invalid utf8-import-module.wast:1172 AGFzbQEAAAACDQEEdGVzdAPugP0DfwA= +malformed invalid utf8-import-module.wast:1187 AGFzbQEAAAACDQEEdGVzdAPvgAADfwA= +malformed invalid utf8-import-module.wast:1202 AGFzbQEAAAACDQEEdGVzdAPvgH8DfwA= +malformed invalid utf8-import-module.wast:1217 AGFzbQEAAAACDQEEdGVzdAPvgMADfwA= +malformed invalid utf8-import-module.wast:1232 AGFzbQEAAAACDQEEdGVzdAPvgP0DfwA= +malformed invalid utf8-import-module.wast:1249 AGFzbQEAAAACDwEEdGVzdAXxgICAgAN/AA== +malformed invalid utf8-import-module.wast:1264 AGFzbQEAAAACDQEEdGVzdAPxgIADfwA= +malformed invalid utf8-import-module.wast:1279 AGFzbQEAAAACDgEEdGVzdATxgIAjA38A +malformed invalid utf8-import-module.wast:1294 AGFzbQEAAAACDAEEdGVzdALxgAN/AA== +malformed invalid utf8-import-module.wast:1309 AGFzbQEAAAACDQEEdGVzdAPxgCMDfwA= +malformed invalid utf8-import-module.wast:1324 AGFzbQEAAAACCwEEdGVzdAHxA38A +malformed invalid utf8-import-module.wast:1339 AGFzbQEAAAACDAEEdGVzdALxIwN/AA== +malformed invalid utf8-import-module.wast:1356 AGFzbQEAAAACDgEEdGVzdATwAJCQA38A +malformed invalid utf8-import-module.wast:1371 AGFzbQEAAAACDgEEdGVzdATwf5CQA38A +malformed invalid utf8-import-module.wast:1386 AGFzbQEAAAACDgEEdGVzdATwgICAA38A +malformed invalid utf8-import-module.wast:1401 AGFzbQEAAAACDgEEdGVzdATwgJCQA38A +malformed invalid utf8-import-module.wast:1416 AGFzbQEAAAACDgEEdGVzdATwj5CQA38A +malformed invalid utf8-import-module.wast:1431 AGFzbQEAAAACDgEEdGVzdATwj7+/A38A +malformed invalid utf8-import-module.wast:1446 AGFzbQEAAAACDgEEdGVzdATwwJCQA38A +malformed invalid utf8-import-module.wast:1461 AGFzbQEAAAACDgEEdGVzdATw/ZCQA38A +malformed invalid utf8-import-module.wast:1476 AGFzbQEAAAACDgEEdGVzdATxAICAA38A +malformed invalid utf8-import-module.wast:1491 AGFzbQEAAAACDgEEdGVzdATxf4CAA38A +malformed invalid utf8-import-module.wast:1506 AGFzbQEAAAACDgEEdGVzdATxwICAA38A +malformed invalid utf8-import-module.wast:1521 AGFzbQEAAAACDgEEdGVzdATx/YCAA38A +malformed invalid utf8-import-module.wast:1536 AGFzbQEAAAACDgEEdGVzdATzAICAA38A +malformed invalid utf8-import-module.wast:1551 AGFzbQEAAAACDgEEdGVzdATzf4CAA38A +malformed invalid utf8-import-module.wast:1566 AGFzbQEAAAACDgEEdGVzdATzwICAA38A +malformed invalid utf8-import-module.wast:1581 AGFzbQEAAAACDgEEdGVzdATz/YCAA38A +malformed invalid utf8-import-module.wast:1596 AGFzbQEAAAACDgEEdGVzdAT0AICAA38A +malformed invalid utf8-import-module.wast:1611 AGFzbQEAAAACDgEEdGVzdAT0f4CAA38A +malformed invalid utf8-import-module.wast:1626 AGFzbQEAAAACDgEEdGVzdAT0kICAA38A +malformed invalid utf8-import-module.wast:1641 AGFzbQEAAAACDgEEdGVzdAT0v4CAA38A +malformed invalid utf8-import-module.wast:1656 AGFzbQEAAAACDgEEdGVzdAT0wICAA38A +malformed invalid utf8-import-module.wast:1671 AGFzbQEAAAACDgEEdGVzdAT0/YCAA38A +malformed invalid utf8-import-module.wast:1686 AGFzbQEAAAACDgEEdGVzdAT1gICAA38A +malformed invalid utf8-import-module.wast:1701 AGFzbQEAAAACDgEEdGVzdAT3gICAA38A +malformed invalid utf8-import-module.wast:1716 AGFzbQEAAAACDgEEdGVzdAT3v7+/A38A +malformed invalid utf8-import-module.wast:1733 AGFzbQEAAAACDgEEdGVzdATwkACQA38A +malformed invalid utf8-import-module.wast:1748 AGFzbQEAAAACDgEEdGVzdATwkH+QA38A +malformed invalid utf8-import-module.wast:1763 AGFzbQEAAAACDgEEdGVzdATwkMCQA38A +malformed invalid utf8-import-module.wast:1778 AGFzbQEAAAACDgEEdGVzdATwkP2QA38A +malformed invalid utf8-import-module.wast:1793 AGFzbQEAAAACDgEEdGVzdATxgACAA38A +malformed invalid utf8-import-module.wast:1808 AGFzbQEAAAACDgEEdGVzdATxgH+AA38A +malformed invalid utf8-import-module.wast:1823 AGFzbQEAAAACDgEEdGVzdATxgMCAA38A +malformed invalid utf8-import-module.wast:1838 AGFzbQEAAAACDgEEdGVzdATxgP2AA38A +malformed invalid utf8-import-module.wast:1853 AGFzbQEAAAACDgEEdGVzdATzgACAA38A +malformed invalid utf8-import-module.wast:1868 AGFzbQEAAAACDgEEdGVzdATzgH+AA38A +malformed invalid utf8-import-module.wast:1883 AGFzbQEAAAACDgEEdGVzdATzgMCAA38A +malformed invalid utf8-import-module.wast:1898 AGFzbQEAAAACDgEEdGVzdATzgP2AA38A +malformed invalid utf8-import-module.wast:1913 AGFzbQEAAAACDgEEdGVzdAT0gACAA38A +malformed invalid utf8-import-module.wast:1928 AGFzbQEAAAACDgEEdGVzdAT0gH+AA38A +malformed invalid utf8-import-module.wast:1943 AGFzbQEAAAACDgEEdGVzdAT0gMCAA38A +malformed invalid utf8-import-module.wast:1958 AGFzbQEAAAACDgEEdGVzdAT0gP2AA38A +malformed invalid utf8-import-module.wast:1975 AGFzbQEAAAACDgEEdGVzdATwkJAAA38A +malformed invalid utf8-import-module.wast:1990 AGFzbQEAAAACDgEEdGVzdATwkJB/A38A +malformed invalid utf8-import-module.wast:2005 AGFzbQEAAAACDgEEdGVzdATwkJDAA38A +malformed invalid utf8-import-module.wast:2020 AGFzbQEAAAACDgEEdGVzdATwkJD9A38A +malformed invalid utf8-import-module.wast:2035 AGFzbQEAAAACDgEEdGVzdATxgIAAA38A +malformed invalid utf8-import-module.wast:2050 AGFzbQEAAAACDgEEdGVzdATxgIB/A38A +malformed invalid utf8-import-module.wast:2065 AGFzbQEAAAACDgEEdGVzdATxgIDAA38A +malformed invalid utf8-import-module.wast:2080 AGFzbQEAAAACDgEEdGVzdATxgID9A38A +malformed invalid utf8-import-module.wast:2095 AGFzbQEAAAACDgEEdGVzdATzgIAAA38A +malformed invalid utf8-import-module.wast:2110 AGFzbQEAAAACDgEEdGVzdATzgIB/A38A +malformed invalid utf8-import-module.wast:2125 AGFzbQEAAAACDgEEdGVzdATzgIDAA38A +malformed invalid utf8-import-module.wast:2140 AGFzbQEAAAACDgEEdGVzdATzgID9A38A +malformed invalid utf8-import-module.wast:2155 AGFzbQEAAAACDgEEdGVzdAT0gIAAA38A +malformed invalid utf8-import-module.wast:2170 AGFzbQEAAAACDgEEdGVzdAT0gIB/A38A +malformed invalid utf8-import-module.wast:2185 AGFzbQEAAAACDgEEdGVzdAT0gIDAA38A +malformed invalid utf8-import-module.wast:2200 AGFzbQEAAAACDgEEdGVzdAT0gID9A38A +malformed invalid utf8-import-module.wast:2217 AGFzbQEAAAACEAEEdGVzdAb4gICAgIADfwA= +malformed invalid utf8-import-module.wast:2232 AGFzbQEAAAACDgEEdGVzdAT4gICAA38A +malformed invalid utf8-import-module.wast:2247 AGFzbQEAAAACDwEEdGVzdAX4gICAIwN/AA== +malformed invalid utf8-import-module.wast:2262 AGFzbQEAAAACDQEEdGVzdAP4gIADfwA= +malformed invalid utf8-import-module.wast:2277 AGFzbQEAAAACDgEEdGVzdAT4gIAjA38A +malformed invalid utf8-import-module.wast:2292 AGFzbQEAAAACDAEEdGVzdAL4gAN/AA== +malformed invalid utf8-import-module.wast:2307 AGFzbQEAAAACDQEEdGVzdAP4gCMDfwA= +malformed invalid utf8-import-module.wast:2322 AGFzbQEAAAACCwEEdGVzdAH4A38A +malformed invalid utf8-import-module.wast:2337 AGFzbQEAAAACDAEEdGVzdAL4IwN/AA== +malformed invalid utf8-import-module.wast:2354 AGFzbQEAAAACDwEEdGVzdAX4gICAgAN/AA== +malformed invalid utf8-import-module.wast:2369 AGFzbQEAAAACDwEEdGVzdAX7v7+/vwN/AA== +malformed invalid utf8-import-module.wast:2386 AGFzbQEAAAACEQEEdGVzdAf8gICAgICAA38A +malformed invalid utf8-import-module.wast:2401 AGFzbQEAAAACDwEEdGVzdAX8gICAgAN/AA== +malformed invalid utf8-import-module.wast:2416 AGFzbQEAAAACEAEEdGVzdAb8gICAgCMDfwA= +malformed invalid utf8-import-module.wast:2431 AGFzbQEAAAACDgEEdGVzdAT8gICAA38A +malformed invalid utf8-import-module.wast:2446 AGFzbQEAAAACDwEEdGVzdAX8gICAIwN/AA== +malformed invalid utf8-import-module.wast:2461 AGFzbQEAAAACDQEEdGVzdAP8gIADfwA= +malformed invalid utf8-import-module.wast:2476 AGFzbQEAAAACDgEEdGVzdAT8gIAjA38A +malformed invalid utf8-import-module.wast:2491 AGFzbQEAAAACDAEEdGVzdAL8gAN/AA== +malformed invalid utf8-import-module.wast:2506 AGFzbQEAAAACDQEEdGVzdAP8gCMDfwA= +malformed invalid utf8-import-module.wast:2521 AGFzbQEAAAACCwEEdGVzdAH8A38A +malformed invalid utf8-import-module.wast:2536 AGFzbQEAAAACDAEEdGVzdAL8IwN/AA== +malformed invalid utf8-import-module.wast:2553 AGFzbQEAAAACEAEEdGVzdAb8gICAgIADfwA= +malformed invalid utf8-import-module.wast:2568 AGFzbQEAAAACEAEEdGVzdAb9v7+/v78DfwA= +malformed invalid utf8-import-module.wast:2585 AGFzbQEAAAACCwEEdGVzdAH+A38A +malformed invalid utf8-import-module.wast:2600 AGFzbQEAAAACCwEEdGVzdAH/A38A +malformed invalid utf8-import-module.wast:2615 AGFzbQEAAAACDAEEdGVzdAL+/wN/AA== +malformed invalid utf8-import-module.wast:2630 AGFzbQEAAAACDgEEdGVzdAQAAP7/A38A +malformed invalid utf8-import-module.wast:2645 AGFzbQEAAAACDAEEdGVzdAL//gN/AA== +malformed invalid utf8-import-module.wast:2660 AGFzbQEAAAACDgEEdGVzdAT//gAAA38A +valid valid extended-const/global.wast:3 AGFzbQEAAAABNw1gAn9/AX9gAAF/YAABfmAAAW9gAX8AYAF+AGABbwBgAAF9YAABfGABfQBgAXwAYAAAYAF/AX8CLwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdApnbG9iYWxfaTY0A34AAzc2AQIDAwECAQIBAgECBAUGBwgHCAkKCwEBAQEBAQEBAQEBAQEAAQEBCwsBAQwBAQsBDAwBAQEBBAUBcAEBAQUDAQABBoIBEX8AQX4LfQBDAABAwAt8AEQAAAAAAAAQwAt+AEJ7C38BQXQLfQFDAABQwQt8AUQAAAAAAAAswAt+AUJxC38AIwALfgAjAQt/AEEUQQJsQQJrQQRqC34AQhRCAn5CAn1CBXwLfwAjAEEqagt+ACMBQip8C28A0G8LbwHQbwtwANBwCwfVBTMFZ2V0LWEAAAVnZXQtYgABBWdldC1yAAIGZ2V0LW1yAAMFZ2V0LXgABAVnZXQteQAFBmdldC16MQAGBmdldC16MgAHBmdldC16MwAIBmdldC16NAAJBmdldC16NQAKBmdldC16NgALBXNldC14AAwFc2V0LXkADQZzZXQtbXIADgVnZXQtMwAPBWdldC00ABAFZ2V0LTcAEQVnZXQtOAASBXNldC03ABMFc2V0LTgAFA9hcy1zZWxlY3QtZmlyc3QAFg1hcy1zZWxlY3QtbWlkABcOYXMtc2VsZWN0LWxhc3QAGA1hcy1sb29wLWZpcnN0ABkLYXMtbG9vcC1taWQAGgxhcy1sb29wLWxhc3QAGw9hcy1pZi1jb25kaXRpb24AHAphcy1pZi10aGVuAB0KYXMtaWYtZWxzZQAeDmFzLWJyX2lmLWZpcnN0AB8NYXMtYnJfaWYtbGFzdAAgEWFzLWJyX3RhYmxlLWZpcnN0ACEQYXMtYnJfdGFibGUtbGFzdAAiFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAJBRhcy1jYWxsX2luZGlyZWN0LW1pZAAlFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAmDmFzLXN0b3JlLWZpcnN0ACcNYXMtc3RvcmUtbGFzdAAoD2FzLWxvYWQtb3BlcmFuZAApFGFzLW1lbW9yeS5ncm93LXZhbHVlACoNYXMtY2FsbC12YWx1ZQAsD2FzLXJldHVybi12YWx1ZQAtD2FzLWRyb3Atb3BlcmFuZAAuC2FzLWJyLXZhbHVlAC8SYXMtbG9jYWwuc2V0LXZhbHVlADASYXMtbG9jYWwudGVlLXZhbHVlADETYXMtZ2xvYmFsLnNldC12YWx1ZQAyEGFzLXVuYXJ5LW9wZXJhbmQAMxFhcy1iaW5hcnktb3BlcmFuZAA0EmFzLWNvbXBhcmUtb3BlcmFuZAA1CQcBAEEACwEjCsQDNgQAIwILBAAjBQsEACMQCwQAIxELBAAjBgsEACMJCwQAIwoLBAAjCwsEACMMCwQAIw0LBAAjDgsEACMPCwYAIAAkBgsGACAAJAkLBgAgACQRCwQAIwMLBAAjBAsEACMHCwQAIwgLBgAgACQHCwYAIAAkCAsCAAsJACMGQQJBAxsLCQBBAiMGQQMbCwkAQQJBAyMGGwsLAAN/IwYQFRAVCwsLAAN/EBUjBhAVCwsLAAN/EBUQFSMGCwsQACMGBH8QFUECBRAVQQMLCwwAQQEEfyMGBUECCwsMAEEABH9BAgUjBgsLDgACfyMGQQINAEEDDwsLDgACf0ECIwYNAEEDDwsLDQACfyMGQQIOAQAACwsNAAJ/QQIjBg4BAAALCwQAIAALDgACfyMGQQJBABEAAAsLDgACf0ECIwZBABEAAAsLDgACf0ECQQAjBhEAAAsLCQAjBkEBNgIACwkAQQAjBjYCAAsHACMGKAIACwYAIwZAAAsEACAACwYAIwYQKwsFACMGDwsFACMGGgsJAAJ/IwYMAAsLCAAjBiEAIAALBgAjBiIACwgAIwYkBiMGCwUAIwZFCwcAIwYjBmwLBwAjAEEBSws= +malformed invalid extended-const/global.wast:391 AGFzbQEAAAACmICAgAABCHNwZWN0ZXN0Cmdsb2JhbF9pMzIDfwI= +malformed invalid extended-const/global.wast:421 AGFzbQEAAAAGhoCAgAABfwJBAAs= +valid valid extended-const/data.wast:5 AGFzbQEAAAAFAwEAAQujARgAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmM= +valid valid extended-const/data.wast:179 AGFzbQEAAAAFAwEAAQsJAQBBAEEqagsA +valid valid extended-const/data.wast:184 AGFzbQEAAAAFAwEAAQsJAQBBKkEAawsA +valid valid extended-const/data.wast:189 AGFzbQEAAAAFAwEAAQsJAQBBAUECbAsA +valid valid extended-const/data.wast:196 AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQABCw8BAEECIwBBAWtBAmpsCwA= +invalid invalid extended-const/elem.wast:536 AGFzbQEAAAABBQFgAAF/AwIBAAQEAXAAAQkJAQRBAAsBEAALCgYBBABBCQs= +valid valid extended-const/elem.wast:705 AGFzbQEAAAABCgJgAAF/YAF/AX8DAwIAAQQEAXAACgcRAQ1jYWxsX2luX3RhYmxlAAEJCgEAQQFBAmoLAQAKDgIEAEEqCwcAIAARAAAL +valid valid extended-const/elem.wast:716 AGFzbQEAAAABCgJgAAF/YAF/AX8DAwIAAQQEAXAACgcRAQ1jYWxsX2luX3RhYmxlAAEJCgEAQQJBAWsLAQAKDgIEAEEqCwcAIAARAAAL +valid valid extended-const/elem.wast:727 AGFzbQEAAAABCgJgAAF/YAF/AX8DAwIAAQQEAXAACgcRAQ1jYWxsX2luX3RhYmxlAAEJCgEAQQJBAmwLAQAKDgIEAEEqCwcAIAARAAAL +valid valid extended-const/elem.wast:740 AGFzbQEAAAABCgJgAAF/YAF/AX8CGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMDAgABBAQBcAAKBxEBDWNhbGxfaW5fdGFibGUAAQkRAQBBAiMAQZkFa0ECamwLAQAKDgIEAEEqCwcAIAARAAAL diff --git a/packages/compiler/test/wasm-spec.test.ts b/packages/compiler/test/wasm-spec.test.ts new file mode 100644 index 000000000..fe935e418 --- /dev/null +++ b/packages/compiler/test/wasm-spec.test.ts @@ -0,0 +1,168 @@ +/* The WebAssembly decoder (src/wasm/binary.ts) against the specification's + * test suite, against V8 on mutations of those modules, and on the inline + * modules of real packages in this workspace. V8 (the Node running the + * test) is the oracle: scriptc must accept exactly the modules Node + * accepts, within the translatable feature level. */ +import { globSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, test } from "vitest"; +import { analyzeWasm } from "../src/wasm/binary.js"; +import { decodeBase64 } from "../src/wasm/discover.js"; + +/** V8's verdict (the compiler's type world declares no WebAssembly value). */ +const v8Validate = (bytes: Uint8Array): boolean => + ( + globalThis as unknown as { WebAssembly: { validate(bytes: Uint8Array): boolean } } + ).WebAssembly.validate(bytes); + +interface SpecCase { + suite: string; + v8: string; + where: string; + bytes: Uint8Array; +} + +const cases: SpecCase[] = readFileSync(join(import.meta.dirname, "wasm-spec-cases.txt"), "utf8") + .split("\n") + .filter((line) => line !== "" && !line.startsWith("#")) + .map((line) => { + const [suite, v8, where, b64] = line.split(" "); + return { + suite: suite!, + v8: v8!, + where: where!, + bytes: new Uint8Array(Buffer.from(b64!, "base64")), + }; + }); + +const accepted = (status: string): boolean => status === "valid" || status === "unsupported"; + +describe("WebAssembly spec test suite", () => { + test("covers the 2.0 core suite and extended constant expressions", () => { + expect(cases.length).toBeGreaterThan(3000); + const extended = cases.filter((c) => c.where.startsWith("extended-const/")); + expect(extended.length).toBeGreaterThan(10); + expect( + extended.filter((c) => analyzeWasm(c.bytes).features.includes("extended-const")).length, + ).toBeGreaterThan(5); + }); + + test("every module gets V8's verdict and needs no other proposal", () => { + const wrong: string[] = []; + for (const c of cases) { + const s = analyzeWasm(c.bytes); + const v8 = v8Validate(c.bytes); + if ((v8 ? "valid" : "invalid") !== c.v8) wrong.push(`${c.where}: V8 changed its verdict`); + // Multiple memories are the one proposal outside the translatable set + // that 2.0 tests exercise (as invalid; V8 accepts them). + const allowed = s.status !== "unsupported" || s.unsupported.join() === "multi-memory"; + if (accepted(s.status) !== v8 || !allowed) { + wrong.push(`${c.where}: ${s.status} ${s.error ?? ""} (V8 ${v8 ? "accepts" : "rejects"})`); + } + } + expect(wrong).toEqual([]); + }); + + test("differs from the 2.0 suite only where WebAssembly 3.0 and V8 relaxed it", () => { + // Multiple memories became valid, and memory.size/grow's reserved byte + // a memory index, so a long LEB128 zero is accepted (multi-memory); + // global initializers and segment offsets may read defined immutable + // globals (GC proposal). + const relaxed = cases.filter((c) => c.suite !== "valid" && c.v8 === "valid"); + expect(relaxed.length).toBeGreaterThan(0); + for (const c of relaxed) { + expect(c.where).toMatch( + /^(?:extended-const\/)?(?:binary|data|elem|global|imports|memory)\.wast:\d+$/, + ); + } + expect(relaxed.length).toBeLessThan(30); + }); +}); + +describe("mutations of the suite's valid modules", () => { + test("never disagree with V8 (deterministic sample)", () => { + const seeds = cases.filter((c) => c.v8 === "valid").map((c) => c.bytes); + let state = 0x9e3779b9; + const random = (n: number): number => { + state ^= state << 13; + state >>>= 0; + state ^= state >>> 17; + state ^= state << 5; + state >>>= 0; + return state % n; + }; + const disagreements: string[] = []; + for (let i = 0; i < 8000; i++) { + const seed = seeds[random(seeds.length)]!; + const at = 8 + random(Math.max(1, seed.length - 8)); + let bytes: Uint8Array; + switch (random(4)) { + case 0: + bytes = Uint8Array.from(seed); + bytes[at] = bytes[at]! ^ (1 << random(8)); + break; + case 1: + bytes = Uint8Array.from(seed); + bytes[at] = random(256); + break; + case 2: + bytes = new Uint8Array([...seed.subarray(0, at), ...seed.subarray(at + 1)]); + break; + default: + bytes = new Uint8Array([...seed.subarray(0, at), random(256), ...seed.subarray(at)]); + } + const s = analyzeWasm(bytes); + if (s.status === "unsupported") continue; + if (accepted(s.status) !== v8Validate(bytes)) { + disagreements.push( + `${s.status} ${s.error ?? ""}: ${Buffer.from(bytes).toString("base64")}`, + ); + } + } + expect(disagreements).toEqual([]); + }); +}); + +describe("modules inlined by packages in this workspace", () => { + // Lockfile-pinned installs: undici's llhttp (with and without SIMD) and + // es-module-lexer, both inlined as base64 string literals. + const root = join(import.meta.dirname, "../../.."); + const inline = (pattern: string): Uint8Array[] => + globSync(join(root, pattern)) + .sort() + .flatMap((file) => + [...readFileSync(file, "utf8").matchAll(/AGFzbQ[A-Za-z0-9+/=]*/g)].map((m) => + decodeBase64(m[0])!, + ), + ); + + test("undici's llhttp: the scalar module translates, the SIMD one needs SIMD", () => { + const scalar = inline( + "node_modules/.pnpm/undici@7.*/node_modules/undici/lib/llhttp/llhttp-wasm.js", + ); + const simd = inline( + "node_modules/.pnpm/undici@7.*/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js", + ); + expect(scalar.length).toBeGreaterThan(0); + expect(simd.length).toBeGreaterThan(0); + for (const bytes of scalar) { + expect(analyzeWasm(bytes)).toMatchObject({ status: "valid", importModules: ["env"] }); + expect(v8Validate(bytes)).toBe(true); + } + for (const bytes of simd) { + expect(analyzeWasm(bytes)).toMatchObject({ status: "unsupported", unsupported: ["simd"] }); + expect(v8Validate(bytes)).toBe(true); + } + }); + + test("es-module-lexer's module is plain MVP", () => { + const modules = inline( + "node_modules/.pnpm/es-module-lexer@1.*/node_modules/es-module-lexer/dist/lexer.js", + ); + expect(modules.length).toBeGreaterThan(0); + for (const bytes of modules) { + expect(analyzeWasm(bytes)).toMatchObject({ status: "valid", imports: 0 }); + expect(v8Validate(bytes)).toBe(true); + } + }); +}); diff --git a/packages/runtime/src/scr_array.c b/packages/runtime/src/scr_array.c index ef64be56d..4ef24e906 100644 --- a/packages/runtime/src/scr_array.c +++ b/packages/runtime/src/scr_array.c @@ -1179,7 +1179,7 @@ static void scr_arr_destroy(void *object) { #ifdef SCR_RC_AUDIT scr_live_arrays--; #endif - scr_weak_dispose(a); + scr_weak_dispose_inline(a); scr_mem_free(a); } } diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index 308c9198d..f630ee5f9 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -4464,7 +4464,9 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { } scr_firing_id = 0; if (scr_exc_pending()) return false; /* uncaught throw in a callback: main handles it */ - if (scr_ready_len > 0 || scr_nt_head != NULL) break; /* drain ticks and microtasks before more timers */ + /* Drain ticks and microtasks, and report the rejections the callback + * left, before more timers (Node's runNextTicks between timers). */ + if (scr_ready_len > 0 || scr_nt_head != NULL || scr_nunhandled > 0) break; } /* The check phase: immediates queued BEFORE this phase started run * now, FIFO; ones a callback queues wait for the next turn (the end @@ -4474,7 +4476,7 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { * every macrotask; pending ticks or a woken fiber restart the turn * first (the timer loop's break above), so the phase only runs on a * drained checkpoint. */ - if (scr_ready_len == 0 && scr_nt_head == NULL && scr_pending_immediates > 0) { + if (scr_ready_len == 0 && scr_nt_head == NULL && scr_nunhandled == 0 && scr_pending_immediates > 0) { size_t end = scr_nimmediates; while (scr_immediates_head < end) { size_t i = scr_immediates_head++; @@ -4490,6 +4492,13 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { scr_closure_release(cb); if (scr_exc_pending()) return false; /* uncaught throw: main handles it */ if (!scr_loop_run_checkpoint()) return false; + /* Node's runNextTicks between immediates also reports the + * rejections the callback left before the next one runs; a + * listener's jobs drain before the next report. */ + while (scr_nunhandled > 0 && (top_level == NULL || top_level->state != SCR_PROM_REJECTED)) { + if (scr_report_unhandled_rejections()) return true; + if (!scr_loop_run_checkpoint()) return false; + } if (top_level != NULL && top_level->state == SCR_PROM_REJECTED) break; } /* Fully drained: reset the cursor so the array reuses its slots. */ @@ -4881,6 +4890,8 @@ struct ScrGen { /* The never-started teardown: drops the packed (already-retained) * arguments the spawn wrapper built. Emitted per generator function. */ void (*drop_args)(void *); + /* scr_gen_set_unstarted_return's hook; NULL for most generators. */ + void (*unstarted_return)(void *); bool is_async; void (*settle_async)(ScrGen *, ScrPromise *); ScrAsyncGenRequest *requests_head; @@ -5440,6 +5451,8 @@ void scr_gen_resume(ScrGen *g) { } } +void scr_gen_set_unstarted_return(ScrGen *g, void (*hook)(void *)) { g->unstarted_return = hook; } + void scr_gen_close(ScrGen *g) { scr_gen_slot_reset(&g->ret); scr_gen_resume_return(g); @@ -5474,13 +5487,16 @@ void scr_gen_resume_return(ScrGen *g) { return; case SCR_GEN_UNSTARTED: /* The body never runs: tear the fiber down cleanly (drop the packed - * arguments) and complete with the parked value. */ + * arguments) and complete with the parked value. The generator is + * done before the hook runs, so a resume from inside it answers done; + * an exception the hook raises stays pending for the caller. */ + g->state = SCR_GEN_DONE; + if (g->unstarted_return != NULL) g->unstarted_return(g->fiber->argpack); if (g->drop_args != NULL) g->drop_args(g->fiber->argpack); else free(g->fiber->argpack); scr_fiber_destroy(g->fiber); g->fiber = NULL; scr_fibers_live--; - g->state = SCR_GEN_DONE; scr_gen_ret_to_out(g); return; default: diff --git a/packages/runtime/src/scr_async.h b/packages/runtime/src/scr_async.h index 7f294ca29..efa98e942 100644 --- a/packages/runtime/src/scr_async.h +++ b/packages/runtime/src/scr_async.h @@ -212,6 +212,11 @@ void scr_process_on_warning(ScrDyn *fn); void scr_process_off_warning(ScrDyn *fn); void scr_process_emit_warning(ScrDyn *args); void scr_emit_warning(const char *name, const char *code, ScrStr *message); +/* node:punycode's first load (scr_async_dyn.c): later calls are module + * cache hits. The first reports DEP0040 when `report` (false for a require + * made from node_modules) — at once for a static ES import, which Node + * links before any module evaluates, otherwise on the next tick. */ +void scr_punycode_warn(bool immediate, bool report); /* scr_emit_warning once a 'warning' listener exists (scr_lib.c; set by the * listener registration): units that cannot link the dyn-async unit route * their warnings through it, and print the default report otherwise. */ diff --git a/packages/runtime/src/scr_async_dyn.c b/packages/runtime/src/scr_async_dyn.c index b7387c13e..95771d457 100644 --- a/packages/runtime/src/scr_async_dyn.c +++ b/packages/runtime/src/scr_async_dyn.c @@ -904,6 +904,27 @@ void scr_warning_emit_deferred(ScrDyn *w) { scr_warning_defer(w, scr_warning_tick); } +static SCR_TL bool scr_punycode_loaded = false; + +void scr_punycode_warn(bool immediate, bool report) { + if (scr_punycode_loaded) return; + scr_punycode_loaded = true; + if (!report) return; + static const char text[] = + "The `punycode` module is deprecated. Please use a userland alternative instead."; + ScrStr *message = scr_str_new(text, sizeof text - 1); + ScrStr *code = scr_str_new("DEP0040", 7); + ScrDyn *w = scr_warning_new(message, "DeprecationWarning", code); + if (immediate) { + if (!scr_process_deprecation_flag_on(0)) scr_warning_dispatch(w); + } else { + scr_warning_emit_deferred(w); + } + scr_dyn_release(w); + scr_str_release(code); + scr_str_release(message); +} + /* The console members' warnings. Node's emitWarning emits on * process.nextTick, so these queue in order and each rides one raw tick * on the shared tick queue; any left at exit are dropped with it. */ diff --git a/packages/runtime/src/scr_blob.c b/packages/runtime/src/scr_blob.c index 5c4f9ba68..817a85e2d 100644 --- a/packages/runtime/src/scr_blob.c +++ b/packages/runtime/src/scr_blob.c @@ -44,7 +44,7 @@ static void *scr_blob_retain_v(void *h) { static void scr_blob_release_v(void *h) { ScrBlob *b = h; if (--b->rc) return; - scr_weak_dispose(b); + scr_weak_dispose_inline(b); scr_blob_store_release(b->store); free(b->parts); scr_str_release(b->type); diff --git a/packages/runtime/src/scr_bytes.c b/packages/runtime/src/scr_bytes.c index 28258a29c..d33f32a97 100644 --- a/packages/runtime/src/scr_bytes.c +++ b/packages/runtime/src/scr_bytes.c @@ -296,7 +296,7 @@ ScrBytes *scr_bytes_as_buffer(ScrBytes *bytes) { void scr_bytes_release(ScrBytes *b) { if (!b || b->rc == SIZE_MAX) return; /* NULL: an uninitialized `let` local */ if (--b->rc == 0) { - scr_weak_dispose(b); + scr_weak_dispose_inline(b); if (b->backing) { scr_bytes_release(b->backing); /* a view: data points into the owner */ } else if (b->shared) { diff --git a/packages/runtime/src/scr_bytes.h b/packages/runtime/src/scr_bytes.h index 4309154c9..993921043 100644 --- a/packages/runtime/src/scr_bytes.h +++ b/packages/runtime/src/scr_bytes.h @@ -389,6 +389,10 @@ ScrBytes *scr_buffer_from_arr(const ScrArr *arr); /* number[] */ ScrBytes *scr_buffer_from_dyn_pooled(const ScrDyn *value, const ScrStr *encoding); ScrBytes *scr_buffer_concat(const ScrArr *list); ScrBytes *scr_buffer_concat_len(const ScrArr *list, double total); +/* The untyped-list forms: Node's ERR_INVALID_ARG_TYPE checks on the list + * and each element, then the typed concat. NULL with the exception pending. */ +ScrBytes *scr_buffer_concat_dyn(const struct ScrDyn *list); +ScrBytes *scr_buffer_concat_len_dyn(const struct ScrDyn *list, double total); /* Drops the pool's slab reference and restores Buffer.poolSize (process * exit audits and library session resets). */ void scr_buffer_pool_reset(void); diff --git a/packages/runtime/src/scr_bytes_io.c b/packages/runtime/src/scr_bytes_io.c index 6fe829eeb..4d7ef5f67 100644 --- a/packages/runtime/src/scr_bytes_io.c +++ b/packages/runtime/src/scr_bytes_io.c @@ -1249,6 +1249,56 @@ static ScrBytes *scr_buffer_from_array_like(const ScrDyn *value, double length) return out; } +/* Buffer.concat over an untyped list: Node's checks in Node's order (an + * Array, then the total of the two-argument form unless the list is empty, + * then every element a Uint8Array, ERR_INVALID_ARG_TYPE otherwise), then the + * typed concat over the elements. NULL with the exception pending. */ +static ScrBytes *scr_buffer_concat_dyn_impl(const ScrDyn *list, double total, bool has_total) { + ScrDyn *materialized = NULL; + if (list->kind == SCR_DYN_TYPED_REF && scr_dyn_typed_ref_array_ops(list) != NULL) { + materialized = scr_dyn_typed_ref_materialize(list); + list = materialized; + } + if (list->kind != SCR_DYN_ARR) { + scr_dyn_arg_type_fail("list", "an instance of Array", list); + scr_dyn_release(materialized); + return NULL; + } + size_t n = list->v.arr.len; + if (has_total && n > 0 && !scr_bytes_validate_off("length", total, 9007199254740991.0)) { + scr_dyn_release(materialized); + return NULL; + } + ScrArr *parts = scr_arr_new(SCR_ELEM_BYTES, (double)n); + for (size_t i = 0; i < n; i++) { + ScrDyn *item = scr_dyn_index_get(list, (double)i); + const ScrDyn *shown = item != NULL ? item : scr_dyn_undefined(); + if (!scr_dyn_bytes_is(shown, SCR_BYTES_U8) || shown->v.bytes->is_data_view) { + char name[40]; + snprintf(name, sizeof name, "list[%zu]", i); + scr_dyn_arg_type_fail(name, "an instance of Buffer or Uint8Array", shown); + scr_dyn_release(item); + scr_arr_release(parts); + scr_dyn_release(materialized); + return NULL; + } + scr_arr_push_ref(parts, scr_bytes_retain(shown->v.bytes)); + scr_dyn_release(item); + } + ScrBytes *out = has_total ? scr_buffer_concat_len(parts, total) : scr_buffer_concat(parts); + scr_arr_release(parts); + scr_dyn_release(materialized); + return out; +} + +ScrBytes *scr_buffer_concat_dyn(const ScrDyn *list) { + return scr_buffer_concat_dyn_impl(list, 0, false); +} + +ScrBytes *scr_buffer_concat_len_dyn(const ScrDyn *list, double total) { + return scr_buffer_concat_dyn_impl(list, total, true); +} + ScrBytes *scr_buffer_from_dyn(const ScrDyn *value, const ScrStr *encoding) { if (value->kind == SCR_DYN_STR) return scr_bytes_from_str(value->v.str, encoding); if (value->kind == SCR_DYN_BYTES) return scr_bytes_convert(SCR_BYTES_U8, value->v.bytes); diff --git a/packages/runtime/src/scr_crypto.h b/packages/runtime/src/scr_crypto.h index 5abfeaa48..70fa94dd2 100644 --- a/packages/runtime/src/scr_crypto.h +++ b/packages/runtime/src/scr_crypto.h @@ -25,6 +25,10 @@ ScrCryptoHash *scr_crypto_hash_retain(ScrCryptoHash *h); void scr_crypto_hash_release(ScrCryptoHash *h); void *scr_crypto_hash_retain_v(void *p); void scr_crypto_hash_release_v(void *p); +/* A Hash or Hmac boxed as a checked-dynamic handle (+1; retains h). The + * emitted to-dyn conversion calls it, and it installs the handle ops on + * first use, so programs that never box a Hash do not link them. */ +ScrDyn *scr_crypto_hash_to_dyn(ScrCryptoHash *h); ScrCryptoHash *scr_crypto_hash_update_str(ScrCryptoHash *h, ScrStr *data); ScrCryptoHash *scr_crypto_hash_update_bytes(ScrCryptoHash *h, ScrBytes *data); ScrCryptoHash *scr_crypto_hash_copy(ScrCryptoHash *h); diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index 69485dd6c..e8e7c73fa 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -26,14 +26,19 @@ * count was already given up (which would over-decrement and free live * data). * - * The main graph phases keep a bounded depth-first walk and defer deeper - * traces to reusable worklists. Ordinary RC destruction and cross-generation - * releases are separate paths. White candidates are recorded during scan - * and filtered after outside roots settle, avoiding a separate gather walk. - * markGray also records the objects it grays and counts those still - * externally referenced (rc > 0 after the trial decrements). When none is, - * every gray object is garbage, which is what scan would find, so the pass - * whitens the recorded set and skips scan. + * The graph phases keep a bounded depth-first walk and defer deeper traces + * to reusable worklists. Ordinary RC destruction and cross-generation + * releases are separate paths. markGray records the objects it grays (up + * to SCR_CYC_GRAY_LIST_MAX) and counts those still externally referenced + * (rc > 0 after the trial decrements), per pass and per component: the + * objects first reached from one candidate. A component with none is + * garbage, which is what scan would find, so the pass dooms it at once. + * For the others, scan blackens from each recorded object that is still + * externally referenced, which restores exactly the survivors, and what + * stays gray is the garbage: no walk whitens it. Only a pass too large to + * record walks scan from the candidates, whitening as it goes, and then + * filters the white set after outside roots settle, avoiding a separate + * gather walk. * * ── generations ────────────────────────────────────────────────────── * A pass costs O(objects reachable from its candidates), and a candidate @@ -225,6 +230,9 @@ typedef struct { size_t trigger; size_t alloc_trigger; size_t gray_referenced; + size_t comp_referenced; + uint32_t gray_comp; + ScrVec live_ranges; size_t scheduled_mature_age; size_t xg_freed; size_t mature_dead; @@ -256,6 +264,9 @@ static _Thread_local ScrCycThread scr_cyc_thread = {.gen_limit = SCR_CYC_OLD}; #define scr_cyc_trigger (scr_cyc_thread.trigger) #define scr_cyc_alloc_trigger (scr_cyc_thread.alloc_trigger) #define scr_gray_referenced (scr_cyc_thread.gray_referenced) +#define scr_comp_referenced (scr_cyc_thread.comp_referenced) +#define scr_gray_comp (scr_cyc_thread.gray_comp) +#define scr_live_ranges (scr_cyc_thread.live_ranges) #define scr_cyc_scheduled_mature_age (scr_cyc_thread.scheduled_mature_age) #define scr_xg_freed (scr_cyc_thread.xg_freed) #define scr_cyc_mature_dead (scr_cyc_thread.mature_dead) @@ -347,7 +358,7 @@ void *scr_cyc_alloc(size_t size, ScrCycType *type) { void scr_cyc_free(void *obj) { scr_cyc_free_inline(obj); } -static void scr_cyc_grow(ScrVec *vec) { +static __attribute__((noinline, cold)) void scr_cyc_grow(ScrVec *vec) { /* Candidate positions are 31-bit (ScrCycHdr.buf). */ if (vec->cap > (size_t)(SCR_CYC_BUFFERED_BIT - 1) / 2) scr_cyc_oom(); vec->cap = vec->cap ? vec->cap * 2 : 64; @@ -368,6 +379,16 @@ static inline void scr_cyc_push_work(ScrVec *vec, void *obj) { if (vec->n > vec->used) vec->used = vec->n; } +/* The same push inline, for the gray list markGray fills once per object; + * the pass records the list's high-water mark itself (scr_cyc_note_used). */ +static inline void scr_cyc_push_gray(ScrVec *vec, void *obj) { + if (__builtin_expect(vec->n == vec->cap, 0)) scr_cyc_grow(vec); + vec->v[vec->n++] = obj; +} +static inline void scr_cyc_note_used(ScrVec *vec) { + if (vec->n > vec->used) vec->used = vec->n; +} + /* ── the candidate-root buffers (one per generation) ──────────────────── */ #ifndef SCR_WORKERS @@ -392,7 +413,6 @@ static SCR_TL ScrVec scr_promote; static SCR_TL ScrVec scr_pending; static SCR_TL ScrVec scr_restore_pending; #endif -#define SCR_CYC_WALK_DEPTH 64 /* The gathered white set (freed after the walk completes). */ #ifndef SCR_WORKERS @@ -547,9 +567,19 @@ static SCR_TL size_t scr_cyc_alloc_trigger = 0; #endif /* Gray objects whose count is above zero after the trial decrements so far * (see scr_mg_visit). markGray records the gray objects in scr_white while - * there are fewer than SCR_CYC_GRAY_LIST_MAX; a larger walk runs scan. */ + * there are fewer than SCR_CYC_GRAY_LIST_MAX; a larger walk runs scan. + * + * The same count is kept per component (see "components" at scr_cyc_pass): + * the objects one candidate's walk grays first carry that walk's number in + * `buf` (gray objects are never buffered during a pass), and + * scr_comp_referenced counts those still externally referenced. + * scr_live_ranges holds (start, end) ranges of scr_white whose component + * was externally referenced and is left to scan. */ #ifndef SCR_WORKERS static SCR_TL size_t scr_gray_referenced = 0; +static SCR_TL size_t scr_comp_referenced = 0; +static SCR_TL uint32_t scr_gray_comp = 0; +static SCR_TL ScrVec scr_live_ranges; #endif #define SCR_CYC_GRAY_LIST_MAX 65536 @@ -982,42 +1012,103 @@ void scr_cyc_on_release(void *obj) { ((child) == NULL || SCR_RC(child) == SIZE_MAX \ || scr_cyc_gen(scr_cyc_hdr(child)) > scr_gen_limit) -/* Trace callbacks forward an integer depth through their opaque context; - * it is never dereferenced. The ABI already carries it through every edge. */ -static void scr_mark_gray(void *obj, uintptr_t depth); +/* The walks are depth-first and recursive up to SCR_CYC_WALK_DEPTH, where + * an object is queued instead (scr_pending, or scr_restore_pending for the + * restoration, which can run inside scan) and traced from the queue. Trace + * callbacks forward that integer depth through their opaque context; it is + * never dereferenced. */ +#define SCR_CYC_WALK_DEPTH 64 + +/* A walk visits a dense cycle-capable array's children itself: the present + * dense elements, which are all scr_arr_trace_v visits when the array has + * no sparse entries, properties or metadata (no `ext`). Each walk gets its + * own copy, out of line, so the per-edge visitors stay small. */ +#define SCR_CYC_ARRAY_WALK(name, visit) \ + static __attribute__((noinline)) void name(const ScrArr *arr, void *ctx) { \ + size_t end = arr->len < arr->cap ? arr->len : arr->cap; \ + size_t filled = arr->filled < end ? arr->filled : end; \ + for (size_t i = 0; i < filled; i++) visit((void *)(uintptr_t)arr->data[i], ctx); \ + for (size_t i = filled; i < end; i++) \ + if (arr->present[i] == SCR_ARR_VALUE) visit((void *)(uintptr_t)arr->data[i], ctx); \ + } + +/* Visit every child of `obj` with VISIT, through ARRAY_WALK for a dense + * cycle-capable array and through the type's trace otherwise. */ +#define SCR_CYC_CHILDREN(obj, type, ARRAY_WALK, VISIT, depth) \ + do { \ + if ((type)->kind == SCR_CYC_KIND_ARRAY && ((const ScrArr *)(obj))->ext == NULL) \ + ARRAY_WALK((const ScrArr *)(obj), (void *)(depth)); \ + else \ + (type)->trace((obj), (VISIT), (void *)(depth)); \ + } while (0) + +static void scr_mg_visit(void *child, void *ctx); +static void scr_sb_visit(void *child, void *ctx); +static void scr_scan_visit(void *child, void *ctx); +SCR_CYC_ARRAY_WALK(scr_mg_array, scr_mg_visit) +SCR_CYC_ARRAY_WALK(scr_sb_array, scr_sb_visit) +SCR_CYC_ARRAY_WALK(scr_scan_array, scr_scan_visit) + +static inline __attribute__((always_inline)) void scr_mark_gray(void *obj, uintptr_t depth); static void scr_mg_visit(void *child, void *ctx) { if (child == NULL || SCR_RC(child) == SIZE_MAX) return; - if (scr_cyc_gen(scr_cyc_hdr(child)) > scr_gen_limit) { + ScrCycHdr *h = scr_cyc_hdr(child); + if (scr_cyc_gen(h) > scr_gen_limit) { scr_cyc_push_work(&scr_xgen_edges, scr_gray_holder); scr_cyc_push_work(&scr_xgen_edges, child); return; } size_t rc = --SCR_RC(child); - if (scr_cyc_color(scr_cyc_hdr(child)) == SCR_CYC_GRAY) { - if (rc == 0) scr_gray_referenced--; + if (scr_cyc_color(h) == SCR_CYC_GRAY) { + if (rc == 0) { + scr_gray_referenced--; + if (h->buf == scr_gray_comp) scr_comp_referenced--; + } return; } scr_mark_gray(child, (uintptr_t)ctx); } -static void scr_mark_gray(void *obj, uintptr_t depth) { +/* markGray reaches `obj` (its count already trial-decremented): gray it, + * number it with the walk's component, record it, count it while it is + * externally referenced, and walk its children. */ +static inline __attribute__((always_inline)) void scr_mark_gray(void *obj, uintptr_t depth) { ScrCycHdr *h = scr_cyc_hdr(obj); - if (scr_cyc_color(h) == SCR_CYC_GRAY) return; scr_cyc_set_color(h, SCR_CYC_GRAY); /* A full pass takes every buffer, so each buffered object it reaches - * leaves its buffer here, in the header write the walk makes anyway. */ - if (scr_gen_limit == SCR_CYC_OLD && !scr_cyc_take) scr_cyc_clear_buffered(h); - if (scr_white.n < SCR_CYC_GRAY_LIST_MAX) scr_cyc_push_work(&scr_white, obj); - if (SCR_RC(obj) > 0) scr_gray_referenced++; + * leaves its buffer here, in the header write that numbers it anyway. A + * limited pass can gray candidates it did not take; they keep their + * buffer position and are simply never counted as members. */ + if (!scr_cyc_is_buffered(h) || (scr_gen_limit == SCR_CYC_OLD && !scr_cyc_take)) + h->buf = scr_gray_comp; + if (scr_white.n < SCR_CYC_GRAY_LIST_MAX) scr_cyc_push_gray(&scr_white, obj); + if (SCR_RC(obj) > 0) { + scr_gray_referenced++; + scr_comp_referenced++; + } if (depth == SCR_CYC_WALK_DEPTH) { scr_cyc_push_work(&scr_pending, obj); return; } void *holder = scr_gray_holder; scr_gray_holder = obj; - scr_cyc_type(h)->trace(obj, scr_mg_visit, (void *)(depth + 1)); + const ScrCycType *type = scr_cyc_type(h); + SCR_CYC_CHILDREN(obj, type, scr_mg_array, scr_mg_visit, depth + 1); scr_gray_holder = holder; } +/* markGray from one (black) candidate, through the queue. */ +static void scr_mark_gray_from(void *root) { + scr_mark_gray(root, 0); + while (scr_pending.n) { + void *obj = scr_pending.v[--scr_pending.n]; + const ScrCycType *type = scr_cyc_type(scr_cyc_hdr(obj)); + scr_gray_holder = obj; + SCR_CYC_CHILDREN(obj, type, scr_mg_array, scr_mg_visit, 1); + } +} +/* scan's restoration: blacken an externally referenced object and + * everything it reaches that is not black yet, giving back the trial + * decrements of the edges it follows. */ static void scr_scan_black(void *obj, uintptr_t depth); static void scr_sb_visit(void *child, void *ctx) { if (SCR_CYC_SKIP(child)) return; @@ -1028,14 +1119,29 @@ static void scr_sb_visit(void *child, void *ctx) { static void scr_scan_black(void *obj, uintptr_t depth) { ScrCycHdr *h = scr_cyc_hdr(obj); scr_cyc_set_color(h, SCR_CYC_BLACK); + /* Drop markGray's component number: a survivor leaves the pass unbuffered + * unless it is a candidate that a limited pass did not take. */ + if (!scr_cyc_is_buffered(h)) scr_cyc_clear_buffered(h); if (scr_cyc_gen(h) < SCR_CYC_OLD) scr_cyc_push_work(&scr_promote, obj); if (depth == SCR_CYC_WALK_DEPTH) { scr_cyc_push_work(&scr_restore_pending, obj); return; } - scr_cyc_type(h)->trace(obj, scr_sb_visit, (void *)(depth + 1)); + const ScrCycType *type = scr_cyc_type(h); + SCR_CYC_CHILDREN(obj, type, scr_sb_array, scr_sb_visit, depth + 1); +} +static void scr_restore_from(void *root) { + scr_scan_black(root, 0); + while (scr_restore_pending.n) { + void *obj = scr_restore_pending.v[--scr_restore_pending.n]; + const ScrCycType *type = scr_cyc_type(scr_cyc_hdr(obj)); + SCR_CYC_CHILDREN(obj, type, scr_sb_array, scr_sb_visit, 1); + } } +/* scan, walking: restore a gray object that is externally referenced, else + * whiten it, record it once (a later outside root can still restore it; + * settle filters the list) and walk its children. */ static void scr_scan(void *obj, uintptr_t depth); static void scr_scan_visit(void *child, void *ctx) { if (SCR_CYC_SKIP(child)) return; @@ -1045,22 +1151,17 @@ static void scr_scan(void *obj, uintptr_t depth) { ScrCycHdr *h = scr_cyc_hdr(obj); if (scr_cyc_color(h) != SCR_CYC_GRAY) return; if (SCR_RC(obj) > 0) { - scr_scan_black(obj, 0); - while (scr_restore_pending.n) { - void *next = scr_restore_pending.v[--scr_restore_pending.n]; - scr_cyc_type(scr_cyc_hdr(next))->trace(next, scr_sb_visit, NULL); - } + scr_restore_from(obj); return; } scr_cyc_set_color(h, SCR_CYC_WHITE); - /* Record each whitened object once. A later outside root can restore it; - * filter the list only after every scan finishes. No gather trace needed. */ scr_cyc_push_work(&scr_white, obj); if (depth == SCR_CYC_WALK_DEPTH) { scr_cyc_push_work(&scr_pending, obj); return; } - scr_cyc_type(h)->trace(obj, scr_scan_visit, (void *)(depth + 1)); + const ScrCycType *type = scr_cyc_type(h); + SCR_CYC_CHILDREN(obj, type, scr_scan_array, scr_scan_visit, depth + 1); } /* ── cross-generation edges ───────────────────────────────────────────── */ @@ -1138,22 +1239,28 @@ static SCR_CYC_COMPACT void scr_xg_release(void *obj) { * spend their time in the trace and teardown calls they make, so this half is * built for size; the graph walk above it stays fully optimized. Returns how * many objects the pass freed. */ -static SCR_CYC_COMPACT size_t scr_cyc_settle(unsigned gen_limit) { +static SCR_CYC_COMPACT size_t scr_cyc_settle(unsigned gen_limit, bool doomed) { size_t dead = 0; scr_cyc_mature_dead = 0; - for (size_t i = 0; i < scr_white.n; i++) { + scr_cyc_note_used(&scr_white); + /* The list holds the dead set: doomed components, and what scan whitened + * (black once a later outside root restored it), or, when the pass found + * nothing externally referenced, every gray object. `doomed` says that + * every entry is a doomed component's already. */ + for (size_t i = 0; i < scr_white.n && !doomed; i++) { void *obj = scr_white.v[i]; ScrCycHdr *h = scr_cyc_hdr(obj); - if (scr_cyc_color(h) != SCR_CYC_WHITE) continue; - /* A limited pass can reach candidates it did not take: out of their - * buffers before they are freed. */ - if (scr_cyc_take && scr_cyc_is_buffered(h)) scr_cyc_on_dead(obj); - scr_cyc_set_color(h, SCR_CYC_DOOMED); - if (gen_limit < SCR_CYC_OLD) scr_cyc_freed(scr_cyc_type(h), gen_limit); - else scr_cyc_mature_dead += scr_cyc_gen(h) == SCR_CYC_MATURE; + unsigned color = scr_cyc_color(h); + if (color == SCR_CYC_BLACK) continue; + if (color != SCR_CYC_DOOMED) { + /* A limited pass can reach candidates it did not take: out of their + * buffers before they are freed. */ + if (scr_cyc_take && scr_cyc_is_buffered(h)) scr_cyc_on_dead(obj); + scr_cyc_set_color(h, SCR_CYC_DOOMED); + } scr_white.v[dead++] = obj; } - scr_white.n = dead; + if (!doomed) scr_white.n = dead; /* Pin the cross-generation targets of everything about to be freed, while * `gen` still holds the values the walk filtered on (promotion below is @@ -1217,13 +1324,19 @@ static SCR_CYC_COMPACT size_t scr_cyc_settle(unsigned gen_limit) { * over every page the dead structure used, and the structures built from * them, and every later walk over those, would miss the cache on most * objects. In address order the blocks come back in the order the bump - * pointers hand out fresh memory. Teardown order is not observable. */ + * pointers hand out fresh memory. Teardown order is not observable. The + * level statistics are counted here, where the teardown reads the type + * anyway; they only steer later passes. */ size_t freed = scr_white.n; if (scr_white.n >= SCR_CYC_ORDER_MIN) scr_mem_order_for_free(scr_white.v, scr_white.n, sizeof(ScrCycHdr)); for (size_t i = 0; i < scr_white.n; i++) { void *obj = scr_white.v[i]; - scr_cyc_type(scr_cyc_hdr(obj))->free_fn(obj); + ScrCycHdr *h = scr_cyc_hdr(obj); + const ScrCycType *type = scr_cyc_type(h); + if (gen_limit < SCR_CYC_OLD) scr_cyc_freed(type, gen_limit); + else scr_cyc_mature_dead += scr_cyc_gen(h) == SCR_CYC_MATURE; + type->free_fn(obj); } scr_white.n = 0; @@ -1299,6 +1412,7 @@ static size_t scr_cyc_pass_done(size_t freed) { scr_cyc_trim(&scr_restore_pending); scr_cyc_trim(&scr_xgen); scr_cyc_trim(&scr_xgen_edges); + scr_cyc_trim(&scr_live_ranges); return freed; } @@ -1369,28 +1483,114 @@ static size_t scr_cyc_pass(unsigned gen_limit) { scr_xgen_edges.n = 0; scr_white.n = 0; scr_gray_referenced = 0; - for (size_t i = 0; i < scr_cands.n; i++) scr_mark_gray(scr_cands.v[i], 0); - while (scr_pending.n) { - void *obj = scr_pending.v[--scr_pending.n]; - scr_gray_holder = obj; - scr_cyc_type(scr_cyc_hdr(obj))->trace(obj, scr_mg_visit, NULL); + scr_live_ranges.n = 0; + /* Components. Each candidate that no earlier walk reached starts a walk + * of its own, and the objects that walk grays first form its component. + * A component whose objects all end the walk at count zero is garbage: + * a count of zero means every reference to the object was trial-deleted, + * i.e. came from an object grayed so far. An earlier component cannot + * hold one (the walk that grayed it would have grayed the target too), + * so every reference comes from inside the component, and a later walk + * cannot add one: its objects would be referencing this component, and + * those references are still counted. scan would whiten exactly this + * set, so it is doomed at once and scan runs only over the components + * that are externally referenced, if any. A limited pass keeps the single + * whole-pass decision below (its untaken candidates cannot carry the + * component number). */ + bool split = scr_cyc_take == NULL; + uint32_t comp = 0; + for (size_t i = 0; i < scr_cands.n; i++) { + void *obj = scr_cands.v[i]; + if (scr_cyc_color(scr_cyc_hdr(obj)) != SCR_CYC_BLACK) continue; /* walked already */ + size_t start = scr_white.n; + scr_gray_comp = split ? ++comp : 0; + scr_comp_referenced = 0; + scr_mark_gray_from(obj); + if (!split) continue; + size_t end = scr_white.n; + if (scr_comp_referenced == 0 && end < SCR_CYC_GRAY_LIST_MAX) { + for (size_t j = start; j < end; j++) scr_cyc_set_color(scr_cyc_hdr(scr_white.v[j]), SCR_CYC_DOOMED); + } else if (scr_live_ranges.n && (uintptr_t)scr_live_ranges.v[scr_live_ranges.n - 1] == start) { + scr_live_ranges.v[scr_live_ranges.n - 1] = (void *)(uintptr_t)end; + } else { + scr_cyc_push_work(&scr_live_ranges, (void *)(uintptr_t)start); + scr_cyc_push_work(&scr_live_ranges, (void *)(uintptr_t)end); + } } - if (scr_gray_referenced == 0 && scr_white.n < SCR_CYC_GRAY_LIST_MAX) { - for (size_t i = 0; i < scr_white.n; i++) scr_cyc_set_color(scr_cyc_hdr(scr_white.v[i]), SCR_CYC_WHITE); - return scr_cyc_pass_done(scr_cyc_settle(gen_limit)); + scr_cyc_note_used(&scr_white); + /* Nothing externally referenced at all: every gray object is garbage, + * including components that looked referenced only because a later + * component held them. settle dooms the recorded set. */ + if (scr_gray_referenced == 0 && scr_white.n < SCR_CYC_GRAY_LIST_MAX) + return scr_cyc_pass_done(scr_cyc_settle(gen_limit, split && scr_live_ranges.n == 0)); + + /* scan, from the recorded set when markGray recorded every gray object: + * the survivors are exactly what the externally referenced gray objects + * reach, so blackening from each of them in turn leaves the garbage gray + * without walking it again, and settle dooms what is still gray. Only + * the referenced components' entries need looking at; the doomed ones + * are settled already. */ + if (scr_white.n < SCR_CYC_GRAY_LIST_MAX) { + /* One sweep over the list, keeping the doomed components' entries and, + * of the referenced components', only those still gray: a survivor + * leaves the list as soon as it is seen black, and an object kept here + * that a later restoration blackens is skipped by settle. */ + size_t kept = 0, from = 0; + size_t nranges = split ? scr_live_ranges.n : 2; + for (size_t k = 0; k < nranges; k += 2) { + size_t start = split ? (uintptr_t)scr_live_ranges.v[k] : 0; + size_t end = split ? (uintptr_t)scr_live_ranges.v[k + 1] : scr_white.n; + if (start > from) memmove(scr_white.v + kept, scr_white.v + from, (start - from) * sizeof *scr_white.v); + kept += start - from; + for (size_t j = start; j < end; j++) { + void *obj = scr_white.v[j]; + if (scr_cyc_color(scr_cyc_hdr(obj)) != SCR_CYC_GRAY) continue; + if (SCR_RC(obj) > 0) scr_restore_from(obj); + else scr_white.v[kept++] = obj; + } + from = end; + } + if (scr_white.n > from) memmove(scr_white.v + kept, scr_white.v + from, (scr_white.n - from) * sizeof *scr_white.v); + scr_white.n = kept + (scr_white.n - from); + return scr_cyc_pass_done(scr_cyc_settle(gen_limit, false)); } - scr_white.n = 0; - for (size_t i = 0; i < scr_cands.n; i++) scr_scan(scr_cands.v[i], 0); + /* Otherwise scan walks from the candidates. Keep only the doomed + * components' entries; scan appends what it whitens. */ + if (!split) { + scr_white.n = 0; + } else if (scr_live_ranges.n) { + size_t kept = 0, from = 0; + for (size_t k = 0; k < scr_live_ranges.n; k += 2) { + size_t start = (uintptr_t)scr_live_ranges.v[k], end = (uintptr_t)scr_live_ranges.v[k + 1]; + memmove(scr_white.v + kept, scr_white.v + from, (start - from) * sizeof *scr_white.v); + kept += start - from; + from = end; + } + memmove(scr_white.v + kept, scr_white.v + from, (scr_white.n - from) * sizeof *scr_white.v); + scr_white.n = kept + (scr_white.n - from); + } + /* Externally referenced candidates first: what they restore is never + * whitened only to be restored again. */ + for (size_t i = 0; i < scr_cands.n; i++) { + void *obj = scr_cands.v[i]; + if (scr_cyc_color(scr_cyc_hdr(obj)) == SCR_CYC_GRAY && SCR_RC(obj) > 0) scr_restore_from(obj); + } + for (size_t i = 0; i < scr_cands.n; i++) { + void *obj = scr_cands.v[i]; + if (scr_cyc_color(scr_cyc_hdr(obj)) == SCR_CYC_GRAY) scr_scan(obj, 0); + } while (scr_pending.n) { void *obj = scr_pending.v[--scr_pending.n]; - /* A later outside root may have restored this deferred white node and - * its descendants already. Never scan a black node's edges again. */ - if (scr_cyc_color(scr_cyc_hdr(obj)) == SCR_CYC_WHITE) - scr_cyc_type(scr_cyc_hdr(obj))->trace(obj, scr_scan_visit, NULL); + /* A later outside root may have restored this deferred white object and + * its descendants already. Never scan a black object's edges again. */ + ScrCycHdr *h = scr_cyc_hdr(obj); + if (scr_cyc_color(h) != SCR_CYC_WHITE) continue; + const ScrCycType *type = scr_cyc_type(h); + SCR_CYC_CHILDREN(obj, type, scr_scan_array, scr_scan_visit, 1); } - return scr_cyc_pass_done(scr_cyc_settle(gen_limit)); + return scr_cyc_pass_done(scr_cyc_settle(gen_limit, false)); } #ifdef SCR_CYC_TESTING @@ -1425,7 +1625,7 @@ void scr_collect_cycles(void) { void scr_cyc_context_cleanup(void) { scr_collect_cycles(); ScrVec *vectors[] = {&scr_roots[SCR_CYC_NURSERY], &scr_roots[SCR_CYC_MATURE], - &scr_roots[SCR_CYC_OLD], &scr_cands, &scr_promote, &scr_pending, &scr_restore_pending, &scr_white, &scr_xgen, &scr_xgen_edges, + &scr_roots[SCR_CYC_OLD], &scr_cands, &scr_promote, &scr_pending, &scr_restore_pending, &scr_white, &scr_xgen, &scr_xgen_edges, &scr_live_ranges, &scr_pub_holes[SCR_CYC_NURSERY], &scr_pub_holes[SCR_CYC_MATURE], &scr_pub_holes[SCR_CYC_OLD]}; for (size_t i = 0; i < sizeof vectors / sizeof vectors[0]; i++) { free(vectors[i]->v); diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 667ceced0..ca21a0685 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -171,9 +171,22 @@ typedef enum { 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_COUNT, } 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. */ +#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) */ typedef struct ScrClosure ScrClosure; /* full definition below (C11 repeat) */ @@ -1162,7 +1175,25 @@ typedef struct ScrDynHandleOps { * lookup): +1 value, or NULL when the class has no member for `key` * (the read answers undefined). NULL hook = no symbol-keyed members. */ ScrDyn *(*symbol_get)(void *h, const ScrDyn *key); + /* Cross-thread structured clone (postMessage, workerData); NULL = + * DataCloneError. See ScrDynHandleTransfer. */ + const struct ScrDynHandleTransfer *transfer; } ScrDynHandleOps; + +/* How a handle crosses threads, supplied by the handle's owning unit so the + * message encoder (scr_message.c) needs no reference to gated units and no + * per-tag registry. share runs on the sending thread: it answers a payload + * that any thread may use, +1, or NULL when this handle cannot be cloned + * (with an exception pending, or DataCloneError is thrown). adopt runs on + * the receiving thread and answers a fresh box over the payload (+1; the + * message keeps its payload reference). drop releases the payload when the + * message is freed, whether or not it was delivered. All three must be + * callable from any thread. */ +typedef struct ScrDynHandleTransfer { + void *(*share)(void *h); + ScrDyn *(*adopt)(void *payload); + void (*drop)(void *payload); +} ScrDynHandleTransfer; ScrDyn *scr_dyn_native_handle_iterator(ScrDyn *source, unsigned selection); /* ── promises in the checked-dynamic tree (SCR_DYN_PROMISE) ──────────────────────────── diff --git a/packages/runtime/src/scr_events.c b/packages/runtime/src/scr_events.c index 670ea10d6..54eb87a0d 100644 --- a/packages/runtime/src/scr_events.c +++ b/packages/runtime/src/scr_events.c @@ -1061,7 +1061,7 @@ static void scr_stdio_cleanup(void) { scr_stdio_write_hook = NULL; for (int i = 0; i < 3; i++) { ScrStdio *stream = &scr_stdio_streams[i]; - scr_weak_dispose(stream); + scr_weak_dispose_inline(stream); scr_dyn_release(stream->write); stream->write = NULL; scr_dyn_release(stream->write_error); diff --git a/packages/runtime/src/scr_exception.c b/packages/runtime/src/scr_exception.c index fb152e078..3a79cf85b 100644 --- a/packages/runtime/src/scr_exception.c +++ b/packages/runtime/src/scr_exception.c @@ -389,6 +389,17 @@ void scr_exc_suppressed_forget(const void *error_object) { } } +void scr_exc_keep_first(ScrCaught *first) { + if (first->kind == SCR_EXC_GENRET) { + scr_caught_release(first); + return; + } + ScrCaught *dropped = scr_exc_take(); + if (dropped) scr_caught_release(dropped); + scr_rethrow(first); + scr_caught_release(first); +} + void scr_exc_suppress(ScrCaught *suppressed) { if (suppressed->kind == SCR_EXC_GENRET) { /* A generator .return() is a return completion, not a thrown error. diff --git a/packages/runtime/src/scr_exception.h b/packages/runtime/src/scr_exception.h index 80fb98957..3c8261561 100644 --- a/packages/runtime/src/scr_exception.h +++ b/packages/runtime/src/scr_exception.h @@ -84,6 +84,10 @@ typedef struct ScrExcCell { ScrTraceFn trace_fn; ScrStackFrame *stack; /* execution state; never moves with an exception */ } ScrExcCell; +/* Executables push and pop source frames inline (emitter.ts, stack-trace + * mode) at this offset on 64-bit targets. */ +_Static_assert(sizeof(void *) != 8 || offsetof(ScrExcCell, stack) == 56, + "ScrExcCell.stack moved: update the emitter's inline frame push"); /* Runtime-internal (fiber machinery in scr_async.c) — never emitted. */ ScrExcCell *scr_exc_swap_cell(ScrExcCell *cell); /* NULL = main's cell */ @@ -172,6 +176,10 @@ void scr_caught_trace_v(void *c, ScrTraceVisit visit, void *ctx); * when scr_json.c builds its dyn view, and scr_exc_suppressed_forget * releases them. */ void scr_exc_suppress(ScrCaught *suppressed); +/* An iterator close that threw while `first` was propagating: the close's + * error is dropped and `first` is raised again (consumed). A generator + * .return() in flight is replaced by the close's error instead. */ +void scr_exc_keep_first(ScrCaught *first); bool scr_exc_suppressed_values(const void *error_object, const ScrCaught **error, const ScrCaught **suppressed); void scr_exc_suppressed_forget(const void *error_object); diff --git a/packages/runtime/src/scr_fetch.c b/packages/runtime/src/scr_fetch.c index f586b96f8..eff2ae6bc 100644 --- a/packages/runtime/src/scr_fetch.c +++ b/packages/runtime/src/scr_fetch.c @@ -2522,7 +2522,7 @@ static SfResponse *sf_response_retain(SfResponse *r) { static void sf_response_release(SfResponse *r) { if (!r || --r->rc > 0) return; - scr_weak_dispose(r); + scr_weak_dispose_inline(r); sf_stream_release(r->body); sf_headers_release(r->headers); scr_str_release(r->url); @@ -4760,7 +4760,7 @@ static void *sf_request_retain_v(void *ptr) { static void sf_request_release_v(void *ptr) { SfRequest *r = ptr; if (--r->rc) return; - scr_weak_dispose(r); + scr_weak_dispose_inline(r); sf_response_release(r->payload); scr_str_release(r->url); scr_str_release(r->method); scr_str_release(r->redirect); sf_signal_release(r->signal); free(r); diff --git a/packages/runtime/src/scr_generator.h b/packages/runtime/src/scr_generator.h index 2c2c0e953..f0ecb9d85 100644 --- a/packages/runtime/src/scr_generator.h +++ b/packages/runtime/src/scr_generator.h @@ -60,6 +60,9 @@ void scr_gen_coro_finish(ScrFiber *self); ScrGen *scr_async_gen_new(void (*entry)(ScrFiber *, void *), void *argpack, void (*drop_args)(void *), void (*settle)(ScrGen *, ScrPromise *)); +/* 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 *)); 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_json.c b/packages/runtime/src/scr_json.c index b23e78241..0567d0b15 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -212,6 +212,8 @@ static void scr_jb_dispose(ScrJsonBuf *b) { scr_jb_init(b); } +void scr_jb_abandon(ScrJsonBuf *b) { scr_jb_dispose(b); } + void scr_jb_putc(ScrJsonBuf *b, char c) { scr_jb_grow(b, 1); b->data[b->len++] = c; @@ -3649,7 +3651,7 @@ ScrDyn *scr_dyn_bind(ScrDyn *target, ScrDyn *const *args, size_t argc) { * always-linked core never references gated units. A missing tag at use * is an internal error: emitted programs install a unit's ops whenever * they can box its handles. */ -static SCR_TL const ScrDynHandleOps *scr_dynh_ops[SCR_DYNH_COUNT]; +static SCR_TL const ScrDynHandleOps *scr_dynh_ops[SCR_DYNH_CAPACITY]; /* BroadcastChannel's EventTarget table (scr_et_broadcast_install). */ SCR_TL const ScrEtBroadcastOps *scr_et_broadcast_ops = NULL; @@ -12816,8 +12818,15 @@ static ScrDyn *scr_native_set_pack(void *ptr) { ScrDyn *scr_dyn_native_set(ScrMap *value) { static const ScrDynHandleOps ops = { - "Set", &scr_map_retain_v, &scr_map_release_v, &scr_native_set_invoke, - &scr_native_set_get, &scr_weak_map_write, NULL, &scr_native_set_pack, + .cls = "Set", + .retain = &scr_map_retain_v, + .release = &scr_map_release_v, + .invoke = &scr_native_set_invoke, + .get = &scr_native_set_get, + .set = &scr_weak_map_write, + .iter_pack = &scr_native_set_pack, + /* Entries live in internal slots: JSON.stringify writes {}, as Node. */ + .json_empty_object = true, }; scr_dyn_handle_install(SCR_DYNH_SET, &ops); return scr_dyn_new_handle(value, SCR_DYNH_SET); @@ -12926,8 +12935,15 @@ static ScrDyn *scr_native_map_pack(void *ptr) { ScrDyn *scr_dyn_native_map(ScrMap *value) { static const ScrDynHandleOps ops = { - "Map", &scr_map_retain_v, &scr_map_release_v, &scr_native_map_invoke, - &scr_native_map_get, &scr_weak_map_write, NULL, &scr_native_map_pack, + .cls = "Map", + .retain = &scr_map_retain_v, + .release = &scr_map_release_v, + .invoke = &scr_native_map_invoke, + .get = &scr_native_map_get, + .set = &scr_weak_map_write, + .iter_pack = &scr_native_map_pack, + /* Entries live in internal slots: JSON.stringify writes {}, as Node. */ + .json_empty_object = true, }; scr_dyn_handle_install(SCR_DYNH_MAP, &ops); return scr_dyn_new_handle(value, SCR_DYNH_MAP); @@ -13045,63 +13061,13 @@ double scr_buffer_byte_length_dyn(ScrDyn *value, ScrStr *encoding) { return 0; } -/* Radix digit generation follows V8's DoubleToRadixStringView algorithm. - * Copyright 2012 the V8 project authors. BSD license in - * ../vendor/v8-number-radix/LICENSE. The decimal path retains Ryū. */ +/* toString(radix) with any radix value: undefined means 10, anything else + * converts with ToNumber (which can run program code) first. */ ScrStr *scr_num_to_string_radix(double value, const ScrDyn *argument) { double requested = 10; if (argument && argument->kind != SCR_DYN_UNDEF && !scr_dyn_number_coerce_js(argument, &requested)) return NULL; - requested = isnan(requested) ? 0 : trunc(requested); - if (requested < 2 || requested > 36 || !isfinite(requested)) { - static const char message[] = "toString() radix argument must be between 2 and 36"; - scr_throw_error_msg(SCR_ERR_RANGE, message, sizeof message - 1); - return NULL; - } - int radix = (int)requested; - if (radix == 10 || !isfinite(value) || value == 0) return scr_f64_to_scrstr(value); - static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; - char buffer[2200]; - const size_t middle = sizeof buffer / 2; - size_t begin = middle, end = middle; - bool negative = value < 0; - value = fabs(value); - double integer = floor(value), fraction = value - integer; - double tolerance = 0.5 * (nextafter(value, INFINITY) - value); - if (tolerance <= 0) tolerance = nextafter(0, INFINITY); - if (fraction >= tolerance) { - buffer[end++] = '.'; - do { - fraction *= radix; - tolerance *= radix; - int digit = (int)fraction; - buffer[end++] = digits[digit]; - fraction -= digit; - if ((fraction > 0.5 || (fraction == 0.5 && (digit & 1))) && fraction + tolerance > 1) { - for (;;) { - end--; - if (end == middle) { integer += 1; break; } - char last = buffer[end]; - digit = last > '9' ? last - 'a' + 10 : last - '0'; - if (digit + 1 < radix) { buffer[end++] = digits[digit + 1]; break; } - } - break; - } - } while (fraction >= tolerance); - } - /* V8's Double::Exponent is the unbiased exponent minus 52. Beyond - * that precision boundary, trailing digits are unrepresented zeros. */ - while (integer / radix >= 0x1p53) { - integer /= radix; - buffer[--begin] = '0'; - } - do { - double remainder = fmod(integer, radix); - buffer[--begin] = digits[(int)remainder]; - integer = (integer - remainder) / radix; - } while (integer > 0); - if (negative) buffer[--begin] = '-'; - return scr_str_new(buffer + begin, end - begin); + return scr_num_to_string_radix_f64(value, requested); } ScrDyn *scr_caught_to_dyn(const ScrCaught *c) { diff --git a/packages/runtime/src/scr_json.h b/packages/runtime/src/scr_json.h index 8a353f40a..e3077703e 100644 --- a/packages/runtime/src/scr_json.h +++ b/packages/runtime/src/scr_json.h @@ -119,6 +119,8 @@ _Static_assert(offsetof(ScrJsonBuf, data) == 0 && offsetof(ScrJsonBuf, len) == s "LLVM backend expects ScrJsonBuf to start with { data, len, cap }"); void scr_jb_init(ScrJsonBuf *b); +/* Release a buffer's text without finishing it (error paths). */ +void scr_jb_abandon(ScrJsonBuf *b); /* Append a borrowed byte span verbatim, including embedded NUL bytes. */ void scr_jb_write(ScrJsonBuf *b, const char *s, size_t n); /* scr_jb_write for text that holds no lone surrogate (fixed text, JSON diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 9307d9b01..af57ab057 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -824,6 +824,26 @@ static void scr_node_debug_capture(const ScrStr *name) { if (name->len == 10 && memcmp(name->data, "NODE_DEBUG", 10) == 0) (void)scr_node_debug_initial(); } +/* Counts writes and deletes of process.env.TZ. The local-time getters keep + * their last conversion for the generation it was made in. */ +#ifdef SCR_WORKERS +static atomic_uint scr_date_tz_generation; +#else +static unsigned scr_date_tz_generation; +#endif + +/* After a write or delete of the process's TZ, local time follows the new + * value, as in Node: tzset(3) rereads it for later localtime calls. */ +static void scr_env_tz_changed(const ScrStr *name) { + /* Compared by character: a string literal would join the merged + * read-only strings of every program that links this file. */ + if (name->len != 2 || name->data[0] != 'T' || name->data[1] != 'Z') return; +#if !defined(_WIN32) && !defined(__wasi__) + tzset(); +#endif + scr_date_tz_generation++; +} + /* process.env.NAME = v — setenv(3): later scr_env_get reads and spawned * children (posix_spawn inherits environ) observe the write, like Node. * Both args borrowed (NUL-terminated ScrStr data); setenv copies. */ @@ -842,6 +862,7 @@ void scr_env_set(const ScrStr *name, const ScrStr *value) { #else setenv(k, v, 1); #endif + scr_env_tz_changed(name); #ifdef SCR_WORKERS done: #endif @@ -861,6 +882,7 @@ void scr_env_unset(const ScrStr *name) { #else unsetenv(name->data); #endif + scr_env_tz_changed(name); } static ScrStr *scr_fs_read_file_core(ScrStr *path, bool env_file, size_t *ascii); @@ -8076,6 +8098,201 @@ void scr_crypto_hash_release(ScrCryptoHash *hash) { void *scr_crypto_hash_retain_v(void *p) { return scr_crypto_hash_retain(p); } void scr_crypto_hash_release_v(void *p) { scr_crypto_hash_release(p); } +/* Node's TypeError for calling a missing member: " is not a + * function", where `what` is the callee's source text, of any length. */ +static void scr_lib_throw_not_a_function(const char *what) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, what); + scr_jb_puts(&b, " is not a function"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&b)); +} + +/* Hash and Hmac values seen as 'unknown' are native handles. update(), + * digest() and Hash's copy() dispatch through the handle, called or read + * as values; the other members Node defines on a Hash or Hmac (the + * Transform and emitter surface) throw a coded SC2020 error, and other + * names read as undefined, as they do in Node. The member names are one + * NUL-separated char array, which the linker drops from programs that + * never box a Hash (an array of string pointers keeps the strings in the + * unit's shared string section). */ +static const char scr_crypto_hash_dyn_members[] = + "_destroy\0_events\0_eventsCount\0_final\0_flush\0_maxListeners\0_options\0_read\0" + "_readableState\0_transform\0_undestroy\0_writableState\0_write\0_writev\0addListener\0" + "allowHalfOpen\0closed\0compose\0constructor\0cork\0destroy\0destroyed\0digest\0drop\0" + "emit\0end\0errored\0eventNames\0every\0filter\0find\0flatMap\0forEach\0getMaxListeners\0" + "isPaused\0iterator\0listenerCount\0listeners\0map\0off\0on\0once\0pause\0pipe\0" + "prependListener\0prependOnceListener\0push\0rawListeners\0read\0readable\0" + "readableAborted\0readableBuffer\0readableDidRead\0readableEncoding\0readableEnded\0" + "readableFlowing\0readableHighWaterMark\0readableLength\0readableObjectMode\0reduce\0" + "removeAllListeners\0removeListener\0resume\0setDefaultEncoding\0setEncoding\0" + "setMaxListeners\0some\0take\0toArray\0uncork\0unpipe\0unshift\0update\0wrap\0writable\0" + "writableBuffer\0writableCorked\0writableEnded\0writableFinished\0writableHighWaterMark\0" + "writableLength\0writableNeedDrain\0writableObjectMode\0write\0"; + +static bool scr_crypto_hash_dyn_member(const ScrCryptoHash *hash, const char *key, size_t len) { + if (len == 4 && memcmp(key, "copy", 4) == 0) return !hash->hmac; + for (const char *name = scr_crypto_hash_dyn_members; *name; name += strlen(name) + 1) + if (strlen(name) == len && memcmp(name, key, len) == 0) return true; + return false; +} + +static void scr_crypto_hash_dyn_unsupported(const ScrCryptoHash *hash) { + static const char hash_msg[] = + "Hash members other than update(), digest() and copy() have no lowering on an 'unknown' " + "value; cast the value to Hash first"; + static const char hmac_msg[] = + "Hmac members other than update() and digest() have no lowering on an 'unknown' value; " + "cast the value to Hmac first"; + if (hash->hmac) scr_throw_error_msg_code(SCR_ERR_ERROR, hmac_msg, sizeof hmac_msg - 1, "SC2020"); + else scr_throw_error_msg_code(SCR_ERR_ERROR, hash_msg, sizeof hash_msg - 1, "SC2020"); +} + +/* The method values: update() is one function for Hash and Hmac, as in + * Node; digest() is one per class, and copy() is Hash's. */ +enum { SCR_HASH_DYN_UPDATE, SCR_HASH_DYN_HASH_DIGEST, SCR_HASH_DYN_HMAC_DIGEST, SCR_HASH_DYN_COPY }; +static const char *const scr_crypto_hash_dyn_method_names[] = {"update", "digest", "digest", "copy"}; +static SCR_TL ScrDyn *scr_crypto_hash_dyn_methods[4]; +static SCR_TL bool scr_crypto_hash_dyn_methods_registered; + +static void scr_crypto_hash_dyn_methods_cleanup(void) { + for (size_t i = 0; i < 4; i++) { + ScrDyn *method = scr_crypto_hash_dyn_methods[i]; + scr_crypto_hash_dyn_methods[i] = NULL; + scr_dyn_release(method); + } +} + +static int scr_crypto_hash_dyn_method_index(const ScrCryptoHash *hash, const char *key, size_t len) { + if (len == 6 && memcmp(key, "update", 6) == 0) return SCR_HASH_DYN_UPDATE; + if (len == 6 && memcmp(key, "digest", 6) == 0) + return hash->hmac ? SCR_HASH_DYN_HMAC_DIGEST : SCR_HASH_DYN_HASH_DIGEST; + if (len == 4 && memcmp(key, "copy", 4) == 0 && !hash->hmac) return SCR_HASH_DYN_COPY; + return -1; +} + +/* Runs method `index` on `hash`; `self` is the receiving box, which + * update() answers. */ +static ScrDyn *scr_crypto_hash_dyn_run(ScrCryptoHash *hash, ScrDyn *self, int index, + ScrDyn *const *args, size_t argc) { + ScrDyn *undefined = scr_dyn_undefined(); + ScrDyn *first = argc > 0 ? args[0] : undefined; + ScrDyn *second = argc > 1 ? args[1] : undefined; + ScrDyn *result = NULL; + if (index == SCR_HASH_DYN_UPDATE) { + ScrCryptoHash *updated = scr_crypto_hash_update_dyn(hash, first, second); + if (updated) { + scr_crypto_hash_release(updated); + result = scr_dyn_retain(self); + } + } else if (index == SCR_HASH_DYN_COPY) { + ScrCryptoHash *copy = scr_crypto_hash_copy_opts(hash, first); + if (copy) { + result = scr_dyn_new_handle(copy, SCR_DYNH_HASH); + scr_crypto_hash_release(copy); + } + } else { + result = scr_crypto_hash_digest_dyn(hash, first); + } + scr_dyn_release(undefined); + return result; +} + +static ScrDyn *scr_crypto_hash_dyn_method_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + int index = (int)scr_box_get_f64(clo->caps[0]); + ScrDyn *self = scr_dyn_this_get(); + bool hash_this = self->kind == SCR_DYN_HANDLE && self->v.handle.tag == SCR_DYNH_HASH; + bool hmac_this = self->kind == SCR_DYN_HANDLE && self->v.handle.tag == SCR_DYNH_HMAC; + bool valid = index == SCR_HASH_DYN_UPDATE ? hash_this || hmac_this + : index == SCR_HASH_DYN_HMAC_DIGEST ? hmac_this + : hash_this; + ScrDyn *result = NULL; + if (valid) { + result = scr_crypto_hash_dyn_run(self->v.handle.ptr, self, index, args, argc); + } else { + static const char msg[] = + "Hash and Hmac methods called with a receiver other than their own Hash or Hmac have no " + "lowering"; + scr_throw_error_msg_code(SCR_ERR_ERROR, msg, sizeof msg - 1, "SC2020"); + } + scr_dyn_release(self); + return result; +} + +static ScrDyn *scr_crypto_hash_dyn_method_value(int index) { + if (!scr_crypto_hash_dyn_methods_registered) { + scr_crypto_hash_dyn_methods_registered = true; + scr_atexit(scr_crypto_hash_dyn_methods_cleanup); + } + if (!scr_crypto_hash_dyn_methods[index]) { + ScrClosure *clo = scr_closure_new(NULL, 1); + clo->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(clo->caps[0], (double)index); + uint32_t arity = index == SCR_HASH_DYN_UPDATE ? 2 : 1; + scr_crypto_hash_dyn_methods[index] = scr_dyn_new_func( + clo, scr_crypto_hash_dyn_method_call, arity, "", scr_crypto_hash_dyn_method_names[index]); + } + return scr_dyn_retain(scr_crypto_hash_dyn_methods[index]); +} + +static ScrDyn *scr_crypto_hash_dyn_invoke(void *h, ScrDyn *self, const char *method, + ScrDyn *const *args, size_t argc, const char *what) { + ScrCryptoHash *hash = h; + size_t len = strlen(method); + int index = scr_crypto_hash_dyn_method_index(hash, method, len); + if (index >= 0) return scr_crypto_hash_dyn_run(hash, self, index, args, argc); + if (scr_crypto_hash_dyn_member(hash, method, len)) { + scr_crypto_hash_dyn_unsupported(hash); + return NULL; + } + scr_lib_throw_not_a_function(what); + return NULL; +} + +static ScrDyn *scr_crypto_hash_dyn_get(void *h, const char *key, size_t len) { + int index = scr_crypto_hash_dyn_method_index(h, key, len); + if (index >= 0) return scr_crypto_hash_dyn_method_value(index); + if (scr_crypto_hash_dyn_member(h, key, len)) scr_crypto_hash_dyn_unsupported(h); + return NULL; +} + +static bool scr_crypto_hash_dyn_set(void *h, const char *key, size_t len, const ScrDyn *value) { + (void)h; + (void)key; + (void)len; + (void)value; + return false; +} + +static void scr_crypto_hash_dyn_install(void) { + static SCR_TL bool installed; + if (installed) return; + installed = true; + static const ScrDynHandleOps hash_ops = { + .cls = "Hash", + .retain = scr_crypto_hash_retain_v, + .release = scr_crypto_hash_release_v, + .invoke = scr_crypto_hash_dyn_invoke, + .get = scr_crypto_hash_dyn_get, + .set = scr_crypto_hash_dyn_set, + }; + static const ScrDynHandleOps hmac_ops = { + .cls = "Hmac", + .retain = scr_crypto_hash_retain_v, + .release = scr_crypto_hash_release_v, + .invoke = scr_crypto_hash_dyn_invoke, + .get = scr_crypto_hash_dyn_get, + .set = scr_crypto_hash_dyn_set, + }; + scr_dyn_handle_install(SCR_DYNH_HASH, &hash_ops); + scr_dyn_handle_install(SCR_DYNH_HMAC, &hmac_ops); +} + +ScrDyn *scr_crypto_hash_to_dyn(ScrCryptoHash *hash) { + scr_crypto_hash_dyn_install(); + return scr_dyn_new_handle(hash, hash->hmac ? SCR_DYNH_HMAC : SCR_DYNH_HASH); +} + static ScrCryptoHash *scr_crypto_hash_update_raw(ScrCryptoHash *hash, const unsigned char *data, size_t len) { if (!scr_crypto_hash_updatable(hash)) return NULL; @@ -9314,9 +9531,7 @@ static ScrDyn *scr_key_object_invoke(void *h, ScrDyn *self, const char *method, scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); return NULL; } - char message[160]; - int n = snprintf(message, sizeof message, "%s is not a function", what); - scr_throw_error_msg(SCR_ERR_TYPE, message, (size_t)n); + scr_lib_throw_not_a_function(what); return NULL; } @@ -10475,9 +10690,14 @@ typedef struct ScrDateParts { * endpoint. The checked wrapper is the public UTC-getter gate; the inner * walk accepts those bounded local-time values too. */ static void scr_date_utc_parts_unchecked(double t, ScrDateParts *out) { - double dayd = floor(t / 86400000.0); - long long msday = (long long)(t - dayd * 86400000.0); - long long z = (long long)dayd + 719468; + /* t is an integral number of milliseconds within about 2^53. */ + long long tms = (long long)t; + long long day = tms / 86400000, msday = tms % 86400000; + if (msday < 0) { + msday += 86400000; + day--; + } + long long z = day + 719468; long long era = (z >= 0 ? z : z - 146096) / 146097; unsigned long long doe = (unsigned long long)(z - era * 146097); unsigned long long yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; @@ -10487,7 +10707,7 @@ static void scr_date_utc_parts_unchecked(double t, ScrDateParts *out) { unsigned long long d = doy - (153 * mp + 2) / 5 + 1; unsigned long long m = mp < 10 ? mp + 3 : mp - 9; if (m <= 2) y++; - long long wday = ((long long)dayd + 4) % 7; + long long wday = (day + 4) % 7; if (wday < 0) wday += 7; out->year = y; out->month = (int)m - 1; @@ -10516,10 +10736,39 @@ static bool scr_date_localtime(double secd, struct tm *out) { #endif } +/* The thread's last local conversion: reading several local fields of one + * Date (getFullYear, getMonth, getDate, getHours, ...) converts it once. + * A change of process.env.TZ starts a new generation. */ +typedef struct { + bool valid; + unsigned generation; + double ms; + ScrDateParts parts; +} ScrDateLocalCache; +static SCR_TL ScrDateLocalCache scr_date_local_cache; + +static bool scr_date_local_parts_uncached(double clipped, ScrDateParts *out); + static bool scr_date_local_parts(double ms, ScrDateParts *out) { if (!isfinite(ms) || fabs(ms) > 8640000000000000.0) return false; double clipped = trunc(ms); - double secd = floor(clipped / 1000.0); + ScrDateLocalCache *c = &scr_date_local_cache; + unsigned generation = scr_date_tz_generation; + if (c->valid && c->ms == clipped && c->generation == generation) { + *out = c->parts; + return true; + } + if (!scr_date_local_parts_uncached(clipped, out)) return false; + c->valid = true; + c->generation = generation; + c->ms = clipped; + c->parts = *out; + return true; +} + +static bool scr_date_local_parts_uncached(double clipped, ScrDateParts *out) { + long long cms = (long long)clipped; + double secd = (double)(cms / 1000 - (cms % 1000 < 0)); struct tm tmv; double basis_secd = secd; if (!scr_date_localtime(basis_secd, &tmv)) { @@ -10660,6 +10909,64 @@ ScrStr *scr_num_to_fixed0(double x) { * the receiver's non-finite arm, as ECMA-262 requires. Invalid precision * raises V8's catchable RangeError text; otherwise the result is +1. The * exact-binary-value digits come from scr_f64_to_fixed (scr_number.c). */ +/* Number.prototype.toString(radix) with a number radix, which + * ToIntegerOrInfinity truncates (NaN to 0). Radix digit generation follows + * V8's DoubleToRadixStringView algorithm. Copyright 2012 the V8 project + * authors. BSD license in ../vendor/v8-number-radix/LICENSE. The decimal + * path retains Ryū. */ +ScrStr *scr_num_to_string_radix_f64(double value, double requested) { + requested = isnan(requested) ? 0 : trunc(requested); + if (requested < 2 || requested > 36 || !isfinite(requested)) { + static const char message[] = "toString() radix argument must be between 2 and 36"; + scr_throw_error_msg(SCR_ERR_RANGE, message, sizeof message - 1); + return NULL; + } + int radix = (int)requested; + if (radix == 10 || !isfinite(value) || value == 0) return scr_f64_to_scrstr(value); + static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + char buffer[2200]; + const size_t middle = sizeof buffer / 2; + size_t begin = middle, end = middle; + bool negative = value < 0; + value = fabs(value); + double integer = floor(value), fraction = value - integer; + double tolerance = 0.5 * (nextafter(value, INFINITY) - value); + if (tolerance <= 0) tolerance = nextafter(0, INFINITY); + if (fraction >= tolerance) { + buffer[end++] = '.'; + do { + fraction *= radix; + tolerance *= radix; + int digit = (int)fraction; + buffer[end++] = digits[digit]; + fraction -= digit; + if ((fraction > 0.5 || (fraction == 0.5 && (digit & 1))) && fraction + tolerance > 1) { + for (;;) { + end--; + if (end == middle) { integer += 1; break; } + char last = buffer[end]; + digit = last > '9' ? last - 'a' + 10 : last - '0'; + if (digit + 1 < radix) { buffer[end++] = digits[digit + 1]; break; } + } + break; + } + } while (fraction >= tolerance); + } + /* V8's Double::Exponent is the unbiased exponent minus 52. Beyond + * that precision boundary, trailing digits are unrepresented zeros. */ + while (integer / radix >= 0x1p53) { + integer /= radix; + buffer[--begin] = '0'; + } + do { + double remainder = fmod(integer, radix); + buffer[--begin] = digits[(int)remainder]; + integer = (integer - remainder) / radix; + } while (integer > 0); + if (negative) buffer[--begin] = '-'; + return scr_str_new(buffer + begin, end - begin); +} + ScrStr *scr_num_to_fixed(double x, double fraction_digits) { double fd = isnan(fraction_digits) ? 0 : trunc(fraction_digits); if (!(fd >= 0 && fd <= 100)) { diff --git a/packages/runtime/src/scr_map.c b/packages/runtime/src/scr_map.c index 8e34533ad..28922296e 100644 --- a/packages/runtime/src/scr_map.c +++ b/packages/runtime/src/scr_map.c @@ -77,7 +77,8 @@ static inline uint64_t scr_map_hash_str(const ScrStr *k) { uint32_t h = k->hash; if (__builtin_expect(h != 0, 1)) return scr_map_hash_widen(h); h = scr_map_hash_fold(k->data, k->len); - if (k->rc != SIZE_MAX) ((ScrStr *)k)->hash = h; + /* A scratch slice is a writable stack copy (scr_str_is_scratch). */ + if (k->rc != SIZE_MAX || scr_str_is_scratch(k)) ((ScrStr *)k)->hash = h; return scr_map_hash_widen(h); #else return scr_map_hash_bytes(k->data, k->len); @@ -696,7 +697,7 @@ static void scr_map_trace(void *o, ScrTraceVisit visit, void *ctx) { static void scr_map_gcfree(void *o) { ScrMap *m = (ScrMap *)o; bool keys = m->traced & SCR_MAP_TRACED_KEY, vals = m->traced & SCR_MAP_TRACED_VAL; - scr_weak_dispose(m); + scr_weak_dispose_inline(m); for (size_t e = 0; e < m->nentries; e++) { if (!m->entries[e].hash) continue; if (!keys) scr_map_release_key(m, m->entries[e].key); @@ -751,7 +752,7 @@ ScrMap *scr_map_retain(ScrMap *m) { static void scr_map_destroy(void *object) { ScrMap *m = object; - scr_weak_dispose(m); + scr_weak_dispose_inline(m); for (size_t e = 0; e < m->nentries; e++) { if (!m->entries[e].hash) continue; scr_map_release_key(m, m->entries[e].key); @@ -931,7 +932,11 @@ static void scr_map_append(ScrMap *m, uint64_t hash, uint64_t key, uint64_t val, m->filter |= scr_map_filter_bits(hash); m->nlive++; if (m->key_kind == SCR_MAP_KEY_STR) { - scr_str_retain((ScrStr *)scr_map_slot_to_ptr(key)); /* key is borrowed */ + ScrStr *k = scr_map_slot_to_ptr(key); /* key is borrowed */ + if (__builtin_expect(scr_str_is_scratch(k), 0)) + m->entries[idx].key = scr_map_slot_from_ptr(scr_str_scratch_keep(k)); + else + scr_str_retain(k); } else if (m->key_kind == SCR_MAP_KEY_DYN) { const ScrDyn *d = (ScrDyn *)scr_map_slot_to_ptr(key); if (d->kind == SCR_DYN_NUM && d->v.num == 0) { diff --git a/packages/runtime/src/scr_message.c b/packages/runtime/src/scr_message.c index 82428a6fe..7c969a2e3 100644 --- a/packages/runtime/src/scr_message.c +++ b/packages/runtime/src/scr_message.c @@ -23,6 +23,8 @@ typedef enum { SCR_MESSAGE_TYPED_REF, /* A transferred MessagePort: its channel end (+1), the side in number. */ SCR_MESSAGE_PORT, + /* A native handle cloned through its unit's ScrDynHandleTransfer. */ + SCR_MESSAGE_HANDLE, } ScrMessageKind; const ScrMessagePortOps *scr_message_port_ops = NULL; @@ -49,6 +51,9 @@ typedef struct { bool data_view; ScrDyn *published; /* SCR_MESSAGE_PUBLISHED: immortal root, owns nothing */ ScrChannel *channel; /* SCR_MESSAGE_PORT */ + /* SCR_MESSAGE_HANDLE: the owning unit's hooks and its payload (+1). */ + const ScrDynHandleTransfer *transfer; + void *payload; /* SCR_MESSAGE_TYPED_REF: the capsule's process-wide adapters and type key * (vtables, adapters and type keys are shared by every thread). */ struct { @@ -389,7 +394,20 @@ static size_t scr_message_value(ScrMessageEncoder *encoder, const ScrDyn *input) break; } bool buffer = scr_buffer_storage_is(value); - if (value->kind == SCR_DYN_HANDLE && !buffer) goto unsupported; + if (value->kind == SCR_DYN_HANDLE && !buffer) { + const ScrDynHandleTransfer *transfer = scr_dyn_handle_ops_of(value)->transfer; + if (!transfer) goto unsupported; + void *payload = transfer->share(value->v.handle.ptr); + if (!payload) { + if (scr_exc_pending()) return SIZE_MAX; + goto unsupported; + } + node = &encoder->message->nodes[id]; + node->kind = SCR_MESSAGE_HANDLE; + node->transfer = transfer; + node->payload = payload; + break; + } const ScrBytes *bytes = buffer ? value->v.handle.ptr : value->v.bytes; size_t storage = scr_message_storage(encoder, bytes); /* Adding the storage node can reallocate the node array. */ @@ -765,6 +783,7 @@ ScrDyn *scr_message_decode(const ScrMessage *message) { case SCR_MESSAGE_PORT: values[i] = scr_message_port_ops->adopt(node->channel, (int)node->number); break; + case SCR_MESSAGE_HANDLE: values[i] = node->transfer->adopt(node->payload); break; } if (!values[i] || scr_exc_pending()) goto done; } @@ -820,6 +839,7 @@ void scr_message_free(ScrMessage *message) { free(node->data); scr_shared_release(node->shared); if (node->kind == SCR_MESSAGE_PORT) scr_message_port_ops->release(node->channel); + if (node->kind == SCR_MESSAGE_HANDLE) node->transfer->drop(node->payload); } free(message->nodes); free(message); diff --git a/packages/runtime/src/scr_message.test.c b/packages/runtime/src/scr_message.test.c index 4e8aa7d62..0c6bd7ae3 100644 --- a/packages/runtime/src/scr_message.test.c +++ b/packages/runtime/src/scr_message.test.c @@ -172,6 +172,115 @@ static void check_documents(void) { scr_message_free(message); } +/* 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. */ +#define TRANSFER_TAG ((ScrDynHandleTag)(SCR_DYNH_CAPACITY - 1)) +typedef struct { + _Atomic size_t rc; + int value; +} TransferBox; +static _Atomic int transfer_shares, transfer_adopts, transfer_drops; +static void *transfer_retain(void *h) { + ((TransferBox *)h)->rc++; + return h; +} +static void transfer_release(void *h) { + if (--((TransferBox *)h)->rc == 0) free(h); +} +static void *transfer_share(void *h) { + TransferBox *source = h; + if (source->value < 0) return NULL; + transfer_shares++; + TransferBox *copy = malloc(sizeof *copy); + copy->rc = 1; + copy->value = source->value; + return copy; +} +static ScrDyn *transfer_adopt(void *payload) { + transfer_adopts++; + return scr_dyn_new_handle(payload, TRANSFER_TAG); +} +static void transfer_drop(void *payload) { + transfer_drops++; + transfer_release(payload); +} +static const ScrDynHandleTransfer transfer_hooks = {transfer_share, transfer_adopt, transfer_drop}; +static const ScrDynHandleOps transfer_ops = { + .cls = "Transferable", + .retain = transfer_retain, + .release = transfer_release, + .transfer = &transfer_hooks, +}; +static const ScrDynHandleOps opaque_ops = { + .cls = "Opaque", + .retain = transfer_retain, + .release = transfer_release, +}; + +static void *receive_handles(void *data) { + scr_context_enter(1); + scr_init(); + scr_lib_init(0, NULL); + scr_dyn_handle_install(TRANSFER_TAG, &transfer_ops); + ScrDyn *root = scr_message_decode(data); + assert(root && !scr_exc_pending()); + ScrDyn *first = scr_dyn_obj_get(root, "first", 5); + assert(first->kind == SCR_DYN_HANDLE && first->v.handle.tag == TRANSFER_TAG); + assert(((TransferBox *)first->v.handle.ptr)->value == 7); + /* One handle reached twice decodes to one box. */ + assert(first == scr_dyn_obj_get(root, "again", 5)); + assert(transfer_adopts == 1); + scr_dyn_release(root); + scr_collect_cycles(); + scr_context_cleanup(); + return NULL; +} + +static ScrDyn *transfer_handle(int value, const ScrDynHandleOps *ops) { + scr_dyn_handle_install(TRANSFER_TAG, ops); + TransferBox *box = malloc(sizeof *box); + box->rc = 1; + box->value = value; + ScrDyn *handle = scr_dyn_new_handle(box, TRANSFER_TAG); + transfer_release(box); + return handle; +} + +static void check_handle_transfer(void) { + ScrDyn *root = scr_dyn_new_obj(); + ScrDyn *handle = transfer_handle(7, &transfer_ops); + scr_dyn_obj_set(root, "first", 5, scr_dyn_retain(handle)); + scr_dyn_obj_set(root, "again", 5, handle); + ScrMessage *message = scr_message_encode(root); + assert(message && !scr_exc_pending()); + assert(transfer_shares == 1); + scr_dyn_release(root); + pthread_t receiver; + assert(pthread_create(&receiver, NULL, receive_handles, message) == 0); + assert(pthread_join(receiver, NULL) == 0); + assert(transfer_drops == 0); + scr_message_free(message); + assert(transfer_drops == 1); + + /* A handle whose unit declines (share answers NULL) and a handle with no + * transfer hooks both throw DataCloneError, releasing what was shared. */ + root = scr_dyn_new_arr(); + scr_dyn_arr_push(root, transfer_handle(1, &transfer_ops)); + scr_dyn_arr_push(root, transfer_handle(-1, &transfer_ops)); + assert(!scr_message_encode(root) && scr_exc_pending()); + scr_exc_clear(); + assert(transfer_shares == 2 && transfer_drops == 2); + scr_dyn_release(root); + root = transfer_handle(3, &opaque_ops); + assert(!scr_message_encode(root) && scr_exc_pending()); + scr_exc_clear(); + assert(transfer_shares == 2); + scr_dyn_release(root); + scr_collect_cycles(); +} + int main(void) { scr_init(); scr_lib_init(0, NULL); @@ -188,6 +297,8 @@ int main(void) { check_getters(); check_rejection(); check_documents(); + check_handle_transfer(); scr_context_cleanup(); puts("message graphs preserve identity, sparse values, byte aliases and getter order"); + puts("native handles cross threads through their transfer hooks"); } diff --git a/packages/runtime/src/scr_message.test.ts b/packages/runtime/src/scr_message.test.ts index b2364a5d8..56b35f1ac 100644 --- a/packages/runtime/src/scr_message.test.ts +++ b/packages/runtime/src/scr_message.test.ts @@ -34,7 +34,8 @@ test.skipIf(process.platform === "win32")( env: { ...process.env, UBSAN_OPTIONS: "halt_on_error=1" }, }); expect(result.stdout).toBe( - "message graphs preserve identity, sparse values, byte aliases and getter order\n", + "message graphs preserve identity, sparse values, byte aliases and getter order\n" + + "native handles cross threads through their transfer hooks\n", ); expect(result.stderr).toBe(""); } finally { diff --git a/packages/runtime/src/scr_number.h b/packages/runtime/src/scr_number.h index 84db27075..f980daeb5 100644 --- a/packages/runtime/src/scr_number.h +++ b/packages/runtime/src/scr_number.h @@ -73,6 +73,7 @@ bool scr_num_is_safe_integer(double x); ScrStr *scr_num_to_exponential(double x); ScrStr *scr_num_to_fixed0(double x); ScrStr *scr_num_to_fixed(double x, double fraction_digits); +ScrStr *scr_num_to_string_radix_f64(double value, double radix); /* Object.is over two numbers — the spec's SameValue on doubles: NaN * equals NaN, +0 differs from -0, everything else is ==. Never throws. */ diff --git a/packages/runtime/src/scr_os.c b/packages/runtime/src/scr_os.c index 76f69588e..2e70d0d85 100644 --- a/packages/runtime/src/scr_os.c +++ b/packages/runtime/src/scr_os.c @@ -27,6 +27,8 @@ #include #include #endif +#include +#include #ifdef __linux__ #include #endif @@ -1332,3 +1334,195 @@ ScrDyn *scr_os_constants(void) { scr_atexit(scr_os_constants_clear); return scr_dyn_retain(os); } + +/* ── node:constants (deprecated, DEP0008) ────────────────────────────── + * Node's lib/constants.js: Object.assign({}, os.constants.dlopen, errno, + * priority, signals, fs.constants, crypto.constants), frozen, with an + * ordinary prototype. The fs group follows node_constants.cc's + * DefineFsConstants order and guards (libuv's UV_* values are fixed on + * POSIX); crypto_table (borrowed) is a compiler-emitted function that + * answers the baked crypto.constants table, called only on first use. One + * object per thread. */ +#define SCR_FS_CONSTANT(o, name) scr_os_constant((o), #name, (double)(name)) + +static void scr_fs_constants(ScrDyn *o) { + scr_os_constant(o, "UV_FS_SYMLINK_DIR", 1); + scr_os_constant(o, "UV_FS_SYMLINK_JUNCTION", 2); +#ifdef O_RDONLY + SCR_FS_CONSTANT(o, O_RDONLY); +#endif +#ifdef O_WRONLY + SCR_FS_CONSTANT(o, O_WRONLY); +#endif +#ifdef O_RDWR + SCR_FS_CONSTANT(o, O_RDWR); +#endif + scr_os_constant(o, "UV_DIRENT_UNKNOWN", 0); + scr_os_constant(o, "UV_DIRENT_FILE", 1); + scr_os_constant(o, "UV_DIRENT_DIR", 2); + scr_os_constant(o, "UV_DIRENT_LINK", 3); + scr_os_constant(o, "UV_DIRENT_FIFO", 4); + scr_os_constant(o, "UV_DIRENT_SOCKET", 5); + scr_os_constant(o, "UV_DIRENT_CHAR", 6); + scr_os_constant(o, "UV_DIRENT_BLOCK", 7); +#ifdef S_IFMT + SCR_FS_CONSTANT(o, S_IFMT); +#endif +#ifdef S_IFREG + SCR_FS_CONSTANT(o, S_IFREG); +#endif +#ifdef S_IFDIR + SCR_FS_CONSTANT(o, S_IFDIR); +#endif +#ifdef S_IFCHR + SCR_FS_CONSTANT(o, S_IFCHR); +#endif +#ifdef S_IFBLK + SCR_FS_CONSTANT(o, S_IFBLK); +#endif +#ifdef S_IFIFO + SCR_FS_CONSTANT(o, S_IFIFO); +#endif +#ifdef S_IFLNK + SCR_FS_CONSTANT(o, S_IFLNK); +#endif +#ifdef S_IFSOCK + SCR_FS_CONSTANT(o, S_IFSOCK); +#endif +#ifdef O_CREAT + SCR_FS_CONSTANT(o, O_CREAT); +#endif +#ifdef O_EXCL + SCR_FS_CONSTANT(o, O_EXCL); +#endif + /* libuv's Windows-only open flags are 0 everywhere else. */ + scr_os_constant(o, "UV_FS_O_FILEMAP", 0); + scr_os_constant(o, "UV_FS_O_TEMPORARY", 0); + scr_os_constant(o, "UV_FS_O_SHORT_LIVED", 0); + scr_os_constant(o, "UV_FS_O_SEQUENTIAL", 0); + scr_os_constant(o, "UV_FS_O_RANDOM", 0); +#ifdef O_NOCTTY + SCR_FS_CONSTANT(o, O_NOCTTY); +#endif +#ifdef O_TRUNC + SCR_FS_CONSTANT(o, O_TRUNC); +#endif +#ifdef O_APPEND + SCR_FS_CONSTANT(o, O_APPEND); +#endif +#ifdef O_DIRECTORY + SCR_FS_CONSTANT(o, O_DIRECTORY); +#endif +#ifdef O_NOATIME + SCR_FS_CONSTANT(o, O_NOATIME); +#endif +#ifdef O_NOFOLLOW + SCR_FS_CONSTANT(o, O_NOFOLLOW); +#endif +#ifdef O_SYNC + SCR_FS_CONSTANT(o, O_SYNC); +#endif +#ifdef O_DSYNC + SCR_FS_CONSTANT(o, O_DSYNC); +#endif +#ifdef O_SYMLINK + SCR_FS_CONSTANT(o, O_SYMLINK); +#endif +#ifdef O_DIRECT + SCR_FS_CONSTANT(o, O_DIRECT); +#endif +#ifdef O_NONBLOCK + SCR_FS_CONSTANT(o, O_NONBLOCK); +#endif +#ifdef S_IRWXU + SCR_FS_CONSTANT(o, S_IRWXU); +#endif +#ifdef S_IRUSR + SCR_FS_CONSTANT(o, S_IRUSR); +#endif +#ifdef S_IWUSR + SCR_FS_CONSTANT(o, S_IWUSR); +#endif +#ifdef S_IXUSR + SCR_FS_CONSTANT(o, S_IXUSR); +#endif +#ifdef S_IRWXG + SCR_FS_CONSTANT(o, S_IRWXG); +#endif +#ifdef S_IRGRP + SCR_FS_CONSTANT(o, S_IRGRP); +#endif +#ifdef S_IWGRP + SCR_FS_CONSTANT(o, S_IWGRP); +#endif +#ifdef S_IXGRP + SCR_FS_CONSTANT(o, S_IXGRP); +#endif +#ifdef S_IRWXO + SCR_FS_CONSTANT(o, S_IRWXO); +#endif +#ifdef S_IROTH + SCR_FS_CONSTANT(o, S_IROTH); +#endif +#ifdef S_IWOTH + SCR_FS_CONSTANT(o, S_IWOTH); +#endif +#ifdef S_IXOTH + SCR_FS_CONSTANT(o, S_IXOTH); +#endif +#ifdef F_OK + SCR_FS_CONSTANT(o, F_OK); +#endif +#ifdef R_OK + SCR_FS_CONSTANT(o, R_OK); +#endif +#ifdef W_OK + SCR_FS_CONSTANT(o, W_OK); +#endif +#ifdef X_OK + SCR_FS_CONSTANT(o, X_OK); +#endif + scr_os_constant(o, "UV_FS_COPYFILE_EXCL", 1); + scr_os_constant(o, "COPYFILE_EXCL", 1); + scr_os_constant(o, "UV_FS_COPYFILE_FICLONE", 2); + scr_os_constant(o, "COPYFILE_FICLONE", 2); + scr_os_constant(o, "UV_FS_COPYFILE_FICLONE_FORCE", 4); + scr_os_constant(o, "COPYFILE_FICLONE_FORCE", 4); +} +#undef SCR_FS_CONSTANT + +static SCR_TL ScrDyn *scr_node_constants_value; +static void scr_node_constants_clear(void) { + ScrDyn *value = scr_node_constants_value; + scr_node_constants_value = NULL; + scr_dyn_release(value); +} + +ScrDyn *scr_node_constants(const ScrDyn *crypto_table) { + if (scr_node_constants_value) return scr_dyn_retain(scr_node_constants_value); + ScrDyn *crypto = scr_dyn_call(crypto_table, NULL, 0, "constants"); + if (!crypto) return NULL; + ScrDyn *o = scr_dyn_new_obj(); + scr_os_dlopen_constants(o); + scr_os_errno_constants(o); + scr_os_constant(o, "PRIORITY_LOW", SCR_OS_PRIORITY_LOW); + scr_os_constant(o, "PRIORITY_BELOW_NORMAL", SCR_OS_PRIORITY_BELOW_NORMAL); + scr_os_constant(o, "PRIORITY_NORMAL", SCR_OS_PRIORITY_NORMAL); + scr_os_constant(o, "PRIORITY_ABOVE_NORMAL", SCR_OS_PRIORITY_ABOVE_NORMAL); + scr_os_constant(o, "PRIORITY_HIGH", SCR_OS_PRIORITY_HIGH); + scr_os_constant(o, "PRIORITY_HIGHEST", SCR_OS_PRIORITY_HIGHEST); + scr_os_signal_constants(o); + scr_fs_constants(o); + if (crypto && crypto->kind == SCR_DYN_OBJ) { + for (size_t i = 0; i < crypto->v.obj.len; i++) { + const ScrDynEntry *entry = &crypto->v.obj.entries[i]; + if (entry->accessor || !entry->value) continue; + scr_dyn_obj_set(o, entry->key, entry->key_len, scr_dyn_retain(entry->value)); + } + } + scr_dyn_release(crypto); + scr_dyn_release(scr_dyn_freeze(o)); + scr_node_constants_value = o; + scr_atexit(scr_node_constants_clear); + return scr_dyn_retain(o); +} diff --git a/packages/runtime/src/scr_os.h b/packages/runtime/src/scr_os.h index 3c5009fc5..219726660 100644 --- a/packages/runtime/src/scr_os.h +++ b/packages/runtime/src/scr_os.h @@ -38,6 +38,10 @@ ScrStr *scr_os_cpus_model(double i); double scr_os_cpus_field(double i, double field); /* os.constants: the per-thread null-prototype constants object (+1). */ ScrDyn *scr_os_constants(void); +/* The deprecated node:constants module object (scr_os.c): the os groups, + * fs.constants and the crypto table the borrowed function answers, merged + * and frozen (+1). */ +ScrDyn *scr_node_constants(const ScrDyn *crypto_table); double scr_os_available_parallelism(void); /* uv_available_parallelism: CPUs this process may use, >= 1 */ /* os.userInfo()'s field trio (pw_name / pw_shell / pw_dir — the passwd * home, not the $HOME cascade). +1 fresh; abort on lookup failure. */ diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index cdc03c966..ad9babd9e 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -92,6 +92,9 @@ typedef struct ScrCycType { * SCR_CYC_KIND_EMITTED (llvm/alloc.ts FIRST_EMITTED_CYC_KIND). */ enum { SCR_CYC_KIND_NONE = 0, + /* Also how the collector recognizes a cycle-capable ScrArr, whose dense + * elements it visits inline (scr_cycle.c SCR_CYC_CHILDREN): reserved for + * scr_array.c's descriptor. */ SCR_CYC_KIND_ARRAY, SCR_CYC_KIND_PROMISE, SCR_CYC_KIND_BOX, @@ -420,8 +423,15 @@ static inline void *scr_cyc_alloc_inline(size_t size, ScrCycType *type) { scr_cyc_live++; return h + 1; } -static inline void scr_cyc_free_inline(void *obj) { +/* scr_weak_dispose's NULL test inline, for the runtime's hot frees: the + * hook stays NULL unless the program creates a weak collection or keeps a + * checked record's source, so the free costs one predictable branch, not a + * call. */ +static inline void scr_weak_dispose_inline(void *obj) { if (scr_weak_dispose_hook) scr_weak_dispose_hook(obj); +} +static inline void scr_cyc_free_inline(void *obj) { + scr_weak_dispose_inline(obj); scr_cyc_live--; if (scr_cyc_gen(scr_cyc_hdr(obj)) == SCR_CYC_OLD) scr_cyc_old_freed++; scr_mem_free(scr_cyc_hdr(obj)); diff --git a/packages/runtime/src/scr_regex.c b/packages/runtime/src/scr_regex.c index b24261a4d..a2e591b0e 100644 --- a/packages/runtime/src/scr_regex.c +++ b/packages/runtime/src/scr_regex.c @@ -1358,7 +1358,129 @@ ScrStr *scr_regex_replace_callback(ScrStr *s, ScrRegex *re, ScrDyn *callback, bo for (size_t i = 0; i < count; i++) scr_dyn_release(matches[i].arguments); free(matches); if (scr_exc_pending()) { - free(output.data); + scr_jb_abandon(&output); + scr_regex_input_dispose(&input); + return NULL; + } + scr_regex_put_span(&output, &input, next, len); + scr_regex_input_dispose(&input); + return scr_jb_finish(&output); +} + +/* One call of a typed replacer (scr_regex_replace_fn's contract): string + * arguments are passed +1 (the closure ABI's owned parameters), the offset + * as a number, and the result is the closure's +1 string. */ +static ScrStr *scr_regex_call_replacer(ScrClosure *f, uint32_t arity, int ncap, + ScrStr *const *caps, double offset, ScrStr *subject) { + void *p[4] = { NULL, NULL, NULL, NULL }; + for (uint32_t i = 0; i < arity && i < 4; i++) { + if ((int)i < ncap) { + if (!caps[i]) scr_trap("scriptc: internal error: typed replacer capture is unset\n"); + p[i] = scr_str_retain(caps[i]); + } else if ((int)i == ncap + 1) { + p[i] = scr_str_retain(subject); + } + } + void *fn = f->fn; + typedef ScrStr *R; + switch (arity) { + case 0: return ((R (*)(ScrClosure *))fn)(f); + case 1: return ((R (*)(ScrClosure *, void *))fn)(f, p[0]); + case 2: + if (ncap == 1) return ((R (*)(ScrClosure *, void *, double))fn)(f, p[0], offset); + return ((R (*)(ScrClosure *, void *, void *))fn)(f, p[0], p[1]); + case 3: + if (ncap == 1) return ((R (*)(ScrClosure *, void *, double, void *))fn)(f, p[0], offset, p[2]); + if (ncap == 2) return ((R (*)(ScrClosure *, void *, void *, double))fn)(f, p[0], p[1], offset); + return ((R (*)(ScrClosure *, void *, void *, void *))fn)(f, p[0], p[1], p[2]); + case 4: + if (ncap == 2) + return ((R (*)(ScrClosure *, void *, void *, double, void *))fn)(f, p[0], p[1], offset, p[3]); + if (ncap == 3) + return ((R (*)(ScrClosure *, void *, void *, void *, double))fn)(f, p[0], p[1], p[2], offset); + if (ncap >= 4) + return ((R (*)(ScrClosure *, void *, void *, void *, void *))fn)(f, p[0], p[1], p[2], p[3]); + break; + default: break; + } + scr_trap("scriptc: internal error: typed replacer signature\n"); +} + +/* scr_regex_replace_callback's algorithm for a typed replacer: the matches + * are collected first, keeping only the captures the replacer reads, then + * the replacer runs once per match in order. */ +ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, ScrClosure *f, uint32_t arity, bool all) { + uint8_t *bc = scr_regex_bc(re); + int flags = lre_get_flags(bc); + bool global = (flags & LRE_FLAG_GLOBAL) != 0; + if (all && !global) { + static const char message[] = "String.prototype.replaceAll called with a non-global RegExp argument"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return NULL; + } + bool sticky = (flags & LRE_FLAG_STICKY) != 0; + bool unicode = (flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS)) != 0; + int capture_count = lre_get_capture_count(bc); + size_t keep = arity < (uint32_t)capture_count ? arity : (size_t)capture_count; + ScrRegexInput input; + scr_regex_input_init(&input, re, bc, s); + int len = input.len; + struct { int start, end; } *matches = NULL; + ScrStr **caps = NULL; + size_t count = 0, capacity = 0; + if (global) re->last_index = 0; + int position = sticky ? scr_regex_start(re, len) : 0; + if (position < 0) position = len + 1; + if (sticky && !global) re->last_index = 0; + while (position <= len && scr_exec(&input, bc, position) == 1) { + uint8_t **capture = input.capture; + int start = scr_capture_index(&input, capture[0]); + int end = scr_capture_index(&input, capture[1]); + if (sticky && !global) re->last_index = end; + if (count == capacity) { + size_t next = capacity ? capacity * 2 : 8; + if (next < capacity || next > SIZE_MAX / sizeof(*matches) / (keep + 1)) scr_regex_oom(); + void *grown = realloc(matches, next * sizeof(*matches)); + if (!grown) scr_regex_oom(); + matches = grown; + if (keep) { + grown = realloc(caps, next * keep * sizeof(*caps)); + if (!grown) scr_regex_oom(); + caps = grown; + } + capacity = next; + } + for (size_t group = 0; group < keep; group++) { + const uint8_t *cs = capture[group * 2], *ce = capture[group * 2 + 1]; + caps[count * keep + group] = + cs && ce ? scr_regex_span(&input, scr_capture_index(&input, cs), scr_capture_index(&input, ce)) + : NULL; + } + matches[count].start = start; + matches[count].end = end; + count++; + if (!global) break; + position = start == end ? scr_advance(&input, end, unicode) : end; + } + ScrJsonBuf output; + scr_jb_init(&output); + int next = 0; + scr_dyn_this_push_dyn(NULL); + for (size_t i = 0; i < count; i++) { + ScrStr *replacement = scr_regex_call_replacer(f, arity, capture_count, caps + i * keep, + (double)matches[i].start, s); + if (scr_exc_pending()) break; + scr_regex_put_span(&output, &input, next, matches[i].start); + scr_jb_put_str(&output, replacement); + scr_str_release(replacement); + next = matches[i].end; + } + scr_dyn_this_pop(); + for (size_t i = 0; i < count * keep; i++) scr_str_release(caps[i]); + free(caps); + free(matches); + if (scr_exc_pending()) { + scr_jb_abandon(&output); scr_regex_input_dispose(&input); return NULL; } diff --git a/packages/runtime/src/scr_regex.h b/packages/runtime/src/scr_regex.h index e34e877de..014e5aa83 100644 --- a/packages/runtime/src/scr_regex.h +++ b/packages/runtime/src/scr_regex.h @@ -80,6 +80,16 @@ ScrStr *scr_regex_flags(ScrRegex *re); /* +1 */ ScrStr *scr_regex_to_string(ScrRegex *re); /* +1 */ /* replace: first match without /g, every match with it. */ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep); +/* replace/replaceAll with a typed replacer closure of `arity` (at most 4) + * parameters returning a string. Parameter i receives what Node passes at + * position i: the match and captures (strings) below the capture count n, + * the match offset (a number) at n, the subject (a string) at n + 1. The + * compiler admits a closure only when every position it declares is one of + * those with the declared kind, and every capture it reads is set in every + * match (scr_regex_replace_callback handles the rest). Borrows all. */ +struct ScrClosure; +ScrStr *scr_regex_replace_fn(ScrStr *s, ScrRegex *re, struct ScrClosure *f, uint32_t arity, + bool all); /* replaceAll: throws Node's TypeError when /g is missing (may-throw). */ ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep); /* split: captured substrings are interleaved with the split pieces; diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 6f3529777..43cf21b0c 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -1999,20 +1999,34 @@ ScrStr *scr_str_slice(ScrStr *s, double start, double end) { return scr_str_slice_units(s, e, from, to); } +/* One slice() or substring() boundary in [0, length]: ToIntegerOrInfinity, + * then slice's end-relative negative or substring's clamp. Conversion to an + * integer truncates toward zero exactly like ToIntegerOrInfinity, so no + * libm call is needed; NaN fails both comparisons and lands on 0, and a + * negative fraction above -1 is -0, which slice treats as 0. */ +static inline size_t scr_slice_unit(double v, size_t length, bool substring) { + if (v >= 0) return v >= (double)length ? length : (size_t)v; + if (substring || !(v > -(double)length)) return 0; + long long unit = (long long)v; /* in (-length, 0] */ + return unit == 0 ? 0 : (size_t)((long long)length + unit); +} + +/* slice()/substring() boundaries in UTF-16 units over the receiver's length. */ +static void scr_str_slice_bounds(ScrStr *s, ScrSidx *e, double start, double end, bool substring, + size_t *from_out, size_t *to_out) { + size_t length = scr_sidx_len(s, e); + size_t from = scr_slice_unit(start, length, substring); + size_t to = scr_slice_unit(end, length, substring); + if (substring && from > to) { size_t swap = from; from = to; to = swap; } + *from_out = from; + *to_out = to; +} + void scr_str_slice_range(ScrStr *s, double start, double end, bool substring, ScrStringSlice *out) { ScrSidx *e = scr_sidx_for(s); - size_t length = scr_sidx_len(s, e); - double a = scr_to_integer_or_infinity(start), b = scr_to_integer_or_infinity(end); size_t from, to; - if (substring) { - from = a <= 0 ? 0 : a >= (double)length ? length : (size_t)a; - to = b <= 0 ? 0 : b >= (double)length ? length : (size_t)b; - if (from > to) { size_t swap = from; from = to; to = swap; } - } else { - from = scr_slice_boundary(a, length); - to = scr_slice_boundary(b, length); - } + scr_str_slice_bounds(s, e, start, end, substring, &from, &to); out->start = from; out->length = to > from ? to - from : 0; out->split = 0; @@ -2024,6 +2038,56 @@ void scr_str_slice_range(ScrStr *s, double start, double end, bool substring, } } +/* Fill a scratch buffer with `units` ASCII bytes (scr_str_is_scratch). */ +static inline ScrStr *scr_str_scratch_fill(ScrStr *scratch, const char *data, size_t units) { + scratch->rc = SIZE_MAX; + scratch->len = units; + scratch->cap = 0; /* below len: the scratch marker */ + scratch->ascii = 1; +#if SCR_STR_HASH_CACHE + scratch->hash = 0; +#endif + scr_copy_bytes(scratch->data, data, units); + scratch->data[units] = '\0'; + return scratch; +} + +ScrStr *scr_str_slice_scratch(ScrStr *s, double start, double end, bool substring, + ScrStr *scratch, size_t bytes) { + size_t capacity = bytes - sizeof(ScrStr) - 1; + if (__builtin_expect(s->ascii, 1)) { + /* Units are bytes: no index entry, and the bounds are byte offsets. */ + size_t length = s->len; + size_t from = scr_slice_unit(start, length, substring); + size_t to = scr_slice_unit(end, length, substring); + if (substring && from > to) { size_t swap = from; from = to; to = swap; } + size_t units = to > from ? to - from : 0; + /* Shorter results are interned statics. */ + if (units >= 2 && units <= capacity) return scr_str_scratch_fill(scratch, s->data + from, units); + return scr_str_slice_units(s, NULL, from, to); + } + ScrSidx *e = scr_sidx_for(s); + size_t from, to; + scr_str_slice_bounds(s, e, start, end, substring, &from, &to); + size_t units = to > from ? to - from : 0; + if (units >= 2 && units <= capacity) { + /* Equal unit and byte counts between two character boundaries prove + * the span is ASCII: every other character takes more bytes than + * UTF-16 units. Non-ASCII results keep the owned copy and its index. */ + bool first, last; + size_t from_b = scr_u16_to_byte_c(s, e, from, &first); + size_t to_b = scr_u16_to_byte_c(s, e, to, &last); + if (!first && !last && to_b - from_b == units) + return scr_str_scratch_fill(scratch, s->data + from_b, units); + } + return scr_str_slice_units(s, e, from, to); +} + +ScrStr *scr_str_scratch_keep(ScrStr *s) { + if (!scr_str_is_scratch(s)) return scr_str_retain(s); + return scr_str_from_span_ascii(s->data, s->len); +} + double scr_str_slice_char_code_at(ScrStr *s, const ScrStringSlice *range, double index) { double unit = scr_to_integer_or_infinity(index); if (!(unit >= 0) || unit >= (double)range->length) return NAN; diff --git a/packages/runtime/src/scr_string.h b/packages/runtime/src/scr_string.h index e4bf09995..425dfaabf 100644 --- a/packages/runtime/src/scr_string.h +++ b/packages/runtime/src/scr_string.h @@ -184,6 +184,14 @@ static inline ScrStr *scr_str_retain(ScrStr *s) { void scr_str_release(ScrStr *s); /* NULL-tolerant (uninitialized locals) */ +/* A transient stack copy of a slice (scr_str_slice_scratch): immortal for + * retain and release, and valid only while its borrowing consumer runs. No + * other string has a capacity below its length. Containers that keep a + * string they were lent store scr_str_scratch_keep's copy instead. */ +static inline bool scr_str_is_scratch(const ScrStr *s) { + return s->rc == SIZE_MAX && s->cap < s->len; +} + /* ── lone surrogates: canonical WTF-8 storage ───────────────────────── * String storage is UTF-8 extended to lone UTF-16 surrogates: an unpaired * surrogate is its generalized 3-byte encoding ED A0..BF 80..BF, while a diff --git a/packages/runtime/src/scr_string_methods.h b/packages/runtime/src/scr_string_methods.h index 537747530..62354142e 100644 --- a/packages/runtime/src/scr_string_methods.h +++ b/packages/runtime/src/scr_string_methods.h @@ -83,6 +83,16 @@ typedef struct ScrStringSlice { void scr_str_slice_range(ScrStr *s, double start, double end, bool substring, ScrStringSlice *out); double scr_str_slice_char_code_at(ScrStr *s, const ScrStringSlice *range, double index); +/* A slice that only borrowing consumers read (string-slices.ts) is written + * into the caller's `bytes`-sized stack buffer as an immortal ASCII string, + * whose header marks it as a scratch copy (scr_str_is_scratch). Results + * that do not fit, are not ASCII, or are interned statics are returned as + * the ordinary +1 slice. Releasing either result is correct. */ +ScrStr *scr_str_slice_scratch(ScrStr *s, double start, double end, bool substring, + ScrStr *scratch, size_t bytes); +/* The owning reference a container stores for `s`: a heap copy of a scratch + * string, else `s` retained. */ +ScrStr *scr_str_scratch_keep(ScrStr *s); /* repeat(count): count < 0 or Infinity is a JS RangeError; scriptc has no * exceptions, so it prints "scriptc: RangeError: Invalid count value" to diff --git a/packages/runtime/src/scr_test.c b/packages/runtime/src/scr_test.c index cb2c81971..cc52513a6 100644 --- a/packages/runtime/src/scr_test.c +++ b/packages/runtime/src/scr_test.c @@ -80,6 +80,7 @@ enum { typedef struct { ScrClosure *cb; int flags; + ScrStr *at; /* "file:line:col" of the hook call, owned; NULL for t.* hooks */ } ScrTestHook; typedef struct { @@ -123,6 +124,7 @@ struct ScrTestCtx { ScrPromise *slot; /* owned; the parent waiting for its next subtest to finish */ struct ScrMockTimers *mock_timers; /* t.mock.timers, made on first use; freed at exit */ bool passed; /* t.passed: set when the body passes, as Node's pass() does */ + bool listed; /* a suite listed in the failing section (a failing root before) */ }; static SCR_TL ScrTestCtx *scr_test_root = NULL; @@ -136,6 +138,18 @@ static SCR_TL long scr_test_ct_tests = 0, scr_test_ct_suites = 0, scr_test_ct_pa scr_test_ct_todo = 0; static SCR_TL double scr_test_t0 = 0; static SCR_TL bool scr_test_suite_hook_failed = false; +/* The promises of async module-level before hooks, which the runner waits + * for before the first top-level test. */ +static SCR_TL ScrPromise **scr_test_root_pending = NULL; +static SCR_TL size_t scr_test_root_npending = 0, scr_test_root_pending_cap = 0; + +/* A module-level before hook threw or rejected: the first failure is kept + * for the top-level tests and suites, later ones are dropped. */ +static void scr_test_root_before_failed(ScrTestCtx *root) { + if (!scr_exc_pending()) return; + if (!root->before_err) root->before_err = scr_exc_take(); + else scr_exc_clear(); +} /* Above 0 while subtests run concurrently: result lines are held, and the * outermost concurrent parent prints its subtrees in declaration order, * as Node's reporter does, once they all finish. */ @@ -164,7 +178,10 @@ ScrTestCtx *scr_testctx_retain(ScrTestCtx *t) { } static void scr_test_hooks_free(ScrTestHooks *h) { - for (size_t i = 0; i < h->n; i++) scr_closure_release(h->v[i].cb); + for (size_t i = 0; i < h->n; i++) { + scr_closure_release(h->v[i].cb); + scr_str_release(h->v[i].at); + } free(h->v); h->v = NULL; h->n = h->cap = 0; @@ -277,7 +294,9 @@ static void scr_test_indent(int depth) { /* "(1.234567ms)" — durations print microsecond-rounded (Node's hrtime * granularity), not with double noise. */ +/* NaN prints nothing: a test cancelled before it started has no duration. */ static void scr_test_print_dur(double ms) { + if (isnan(ms)) return; char num[32]; fputs(" (", stdout); size_t n = scr_f64_to_str(round(ms * 1e6) / 1e6, num); @@ -409,10 +428,14 @@ static void scr_test_mocks_restore(ScrTestHooks *mocks, bool forget) { /* `ctx` is the context a hook that takes one receives: the suite or test * for its own before/after hooks, the test for the beforeEach/afterEach * chain. */ -static void scr_test_run_hooks_of(ScrTestCtx *owner, int which, ScrTestCtx *ctx) { - for (size_t i = 0; i < owner->hooks[which].n && !scr_exc_pending(); i++) { - scr_test_call(owner->hooks[which].v[i].cb, owner->hooks[which].v[i].flags, ctx, NULL); +static ScrTestHook *scr_test_run_hooks_of(ScrTestCtx *owner, int which, ScrTestCtx *ctx) { + if (scr_exc_pending()) return NULL; /* an earlier hook in the chain threw */ + for (size_t i = 0; i < owner->hooks[which].n; i++) { + ScrTestHook *h = &owner->hooks[which].v[i]; + scr_test_call(h->cb, h->flags, ctx, NULL); + if (scr_exc_pending()) return h; /* the hook that threw */ } + return NULL; } /* Returns whether the chain has any `which` hooks. */ @@ -559,6 +582,36 @@ static void scr_test_finish_concurrent(ScrTestCtx *t) { for (size_t i = 0; i < t->nchildren; i++) scr_test_report_tree(t->children[i]); } +/* A failing root before hook (Node): each top-level test fails with its + * error without running its body or beforeEach hooks, though its + * afterEach hooks run; a top-level suite fails with it, and the tests and + * suites in it are cancelled before they start. */ +static void scr_test_cancel_unrun(ScrTestCtx *c) { + for (size_t i = 0; i < c->nchildren; i++) scr_test_cancel_unrun(c->children[i]); + static const char msg[] = "test did not finish before its parent and was cancelled"; + scr_throw_str(scr_str_new(msg, sizeof msg - 1)); + c->err = scr_exc_take(); + c->listed = true; + c->dur_ms = NAN; + c->state = SCR_TS_CANCELLED; + scr_test_report_result(c); +} + +static void scr_test_fail_unrun(ScrTestCtx *c, ScrCaught *err) { + double start = scr_now_ms(); + c->err = scr_caught_retain(err); + c->listed = true; + if (c->is_suite) { + for (size_t i = 0; i < c->nchildren; i++) scr_test_cancel_unrun(c->children[i]); + } else { + scr_test_run_each_hooks(c, SCR_HOOK_AFTER_EACH, false); + if (scr_exc_pending()) scr_exc_clear(); + } + c->dur_ms = scr_now_ms() - start; + c->state = SCR_TS_FAIL; + scr_test_report_result(c); +} + static void scr_test_run_children(ScrTestCtx *t) { /* `only` at this level: when a child is marked, or contains a marked * test or suite, only those children run. The others are dropped entirely @@ -572,6 +625,10 @@ static void scr_test_run_children(ScrTestCtx *t) { /* Subtests t.test already ran INLINE at their call site, or started on * their own fiber. */ if (c->state != SCR_TS_PENDING || c->done) continue; + if (t == scr_test_root && t->before_err) { + scr_test_fail_unrun(c, t->before_err); + continue; + } if (t->concurrency > 1) scr_test_start(c); else scr_test_run_node(c); } @@ -813,7 +870,7 @@ static void scr_test_print_failures(ScrTestCtx *t, bool *any) { for (size_t i = 0; i < t->nchildren; i++) { ScrTestCtx *c = t->children[i]; scr_test_print_failures(c, any); - if (!c->is_suite && + if ((!c->is_suite || c->listed) && (c->state == SCR_TS_FAIL || c->state == SCR_TS_TODO_FAIL || c->state == SCR_TS_CANCELLED) && c->err) { @@ -1404,7 +1461,41 @@ static void scr_test_runner_entry(ScrFiber *self, void *argpack) { * the first test only after the file's synchronous body completes. */ scr_await_hop(); ScrTestCtx *root = scr_test_root; + /* The root's before hooks ran at their before() calls; the first test + * waits for the async ones. */ + for (size_t i = 0; i < scr_test_root_npending; i++) { + scr_await_void(scr_test_root_pending[i]); + scr_test_root_before_failed(root); + scr_promise_release(scr_test_root_pending[i]); + } + free(scr_test_root_pending); + scr_test_root_pending = NULL; + scr_test_root_npending = scr_test_root_pending_cap = 0; scr_test_run_children(root); + /* Its after hooks: a throw reports the root as "✖ " with the hook's + * location, after the top-level results, without counting it or changing + * the exit code, as Node does. */ + double after_start = scr_now_ms(); + ScrTestHook *after_failed = scr_test_run_hooks_of(root, SCR_HOOK_AFTER, root); + if (scr_exc_pending()) { + root->err = scr_exc_take(); + root->dur_ms = scr_now_ms() - after_start; + root->state = SCR_TS_FAIL; + if (after_failed && after_failed->at) { + root->at = scr_str_retain(after_failed->at); + /* The name is the file: the location without its ":line:col". */ + size_t end = root->at->len; + for (int colons = 0; end > 0 && colons < 2; end--) { + if (root->at->data[end - 1] == ':') colons++; + } + scr_str_release(root->name); + root->name = scr_str_new(root->at->data, end); + } + fputs("\xE2\x9C\x96 ", stdout); + scr_test_print_str(root->name); + scr_test_print_dur(root->dur_ms); + fputc('\n', stdout); + } scr_test_run_done = true; scr_snap_write_all(); scr_test_count(root); @@ -1418,6 +1509,10 @@ static void scr_test_runner_entry(ScrFiber *self, void *argpack) { scr_test_info_line("duration_ms", round((scr_now_ms() - scr_test_t0) * 1e6) / 1e6); bool any = false; scr_test_print_failures(root, &any); + if (root->err) { + if (!any) fputs("\n\xE2\x9C\x96 failing tests:\n", stdout); + scr_test_print_failure(root); + } /* The tree is done reporting: release it (test bodies may still hold * their TestContext refs — the boxes stay alive through those). */ scr_test_reg_parent = NULL; @@ -1534,7 +1629,41 @@ void scr_test_suite(ScrStr *name /*borrowed*/, double mode, ScrStr *msg /*borrow scr_closure_release(cb); } -void scr_test_hook(double which, ScrClosure *cb /*moves*/, double flags) { +/* A module-level before hook runs at its before() call, as Node runs the + * hooks of its already-started root at once: the synchronous part runs + * now, on the caller's stack, and the runner waits for an async hook's + * promise before the first top-level test. The first failure fails the + * top-level tests and suites (scr_test_fail_unrun). */ +static void scr_test_root_before_now(ScrTestCtx *root, ScrClosure *cb, int flags) { + bool takes_ctx = (flags & SCR_TEST_F_CTX) != 0; + if (flags & SCR_TEST_F_ASYNC) { + ScrPromise *p = + takes_ctx + ? ((ScrPromise * (*)(ScrClosure *, ScrTestCtx *)) cb->fn)(cb, scr_testctx_retain(root)) + : ((ScrPromise * (*)(ScrClosure *)) cb->fn)(cb); + if (p && !scr_exc_pending()) { + if (scr_test_root_npending == scr_test_root_pending_cap) { + scr_test_root_pending_cap = scr_test_root_pending_cap ? scr_test_root_pending_cap * 2 : 2; + scr_test_root_pending = realloc(scr_test_root_pending, + scr_test_root_pending_cap * sizeof *scr_test_root_pending); + if (!scr_test_root_pending) scr_test_oom(); + } + /* The runner observes it: a rejection is the hook's failure, not an + * unhandled rejection. */ + scr_promise_mark_handled(p); + scr_test_root_pending[scr_test_root_npending++] = p; + return; + } + scr_promise_release(p); + } else if (takes_ctx) { + ((void (*)(ScrClosure *, ScrTestCtx *))cb->fn)(cb, scr_testctx_retain(root)); + } else { + ((void (*)(ScrClosure *))cb->fn)(cb); + } + scr_test_root_before_failed(root); +} + +void scr_test_hook(double which, ScrClosure *cb /*moves*/, double flags, ScrStr *at) { scr_test_ensure_root(); ScrTestCtx *owner = scr_test_parent_get(); ScrTestHooks *h = &owner->hooks[(int)which]; @@ -1545,7 +1674,10 @@ void scr_test_hook(double which, ScrClosure *cb /*moves*/, double flags) { } h->v[h->n].cb = cb; h->v[h->n].flags = (int)flags; + h->v[h->n].at = at && at->len > 0 ? scr_str_retain(at) : NULL; h->n++; + if ((int)which == SCR_HOOK_BEFORE && owner == scr_test_root) + scr_test_root_before_now(owner, cb, (int)flags); } /* t.test(...): registers under `t` and — because subtests run inline on @@ -1620,6 +1752,7 @@ void scr_test_ctx_hook(ScrTestCtx *t, double which, ScrClosure *cb /*moves*/, do } h->v[h->n].cb = cb; h->v[h->n].flags = (int)flags; + h->v[h->n].at = NULL; h->n++; } @@ -1631,6 +1764,7 @@ static void scr_test_mocks_add(ScrTestHooks *h, ScrClosure *restore /*moves*/) { } h->v[h->n].cb = restore; h->v[h->n].flags = 0; + h->v[h->n].at = NULL; h->n++; } @@ -2172,6 +2306,7 @@ void scr_test_ctx_diagnostic(ScrTestCtx *t, ScrStr *msg /*borrowed*/) { } ScrStr *scr_test_ctx_name(ScrTestCtx *t) { + if (!t->name && !t->parent) return scr_str_new("", 6); /* Node's root test */ return t->name ? scr_str_retain(t->name) : scr_str_new("", 0); } diff --git a/packages/runtime/src/scr_test.h b/packages/runtime/src/scr_test.h index 26f8928f3..64a280bf7 100644 --- a/packages/runtime/src/scr_test.h +++ b/packages/runtime/src/scr_test.h @@ -33,8 +33,10 @@ void scr_test_register(ScrStr *name, double mode, ScrStr *msg, ScrClosure *cb, d void scr_test_suite(ScrStr *name, double mode, ScrStr *msg, ScrClosure *cb, ScrStr *at, double flags, double concurrency); /* before/after/beforeEach/afterEach on the CURRENT registration parent - * (`which`: 0 before, 1 after, 2 beforeEach, 3 afterEach). */ -void scr_test_hook(double which, ScrClosure *cb, double flags); + * (`which`: 0 before, 1 after, 2 beforeEach, 3 afterEach); `at` is the + * hook call's "file:line:col". The root's before and after hooks run + * around the top-level tests. */ +void scr_test_hook(double which, ScrClosure *cb, double flags, ScrStr *at); /* t.test(...): registers under `t`, runs the subtest INLINE on the * runner fiber, and returns the settled promise the `await` consumes (+1). */ ScrPromise *scr_test_sub(ScrTestCtx *t, ScrStr *name, double mode, ScrStr *msg, ScrClosure *cb, double flags, ScrStr *at, diff --git a/packages/runtime/src/scr_url.c b/packages/runtime/src/scr_url.c index eeadba98f..67de82471 100644 --- a/packages/runtime/src/scr_url.c +++ b/packages/runtime/src/scr_url.c @@ -1989,3 +1989,342 @@ ScrBytes *scr_url_checked_to_path_buffer(const ScrDyn *value, const ScrDyn *opti scr_url_release(url); return result; } + +/* ── node:punycode (deprecated, DEP0040) ─────────────────────────────── + * A port of the punycode.js 2.1.0 that Node 24 bundles as lib/punycode.js. + * It works on UTF-16 code units: ucs2.decode pairs surrogates and keeps + * lone ones, which is exactly a code-point walk over WTF-8 storage. + * Errors throw the module's RangeErrors ("Overflow: ...", "Illegal input + * >= 0x80 ...", "Invalid input"); decoded code points past U+10FFFF throw + * String.fromCodePoint's "Invalid code point N". All inputs are borrowed; + * results are +1, or NULL with the error pending. The DEP0040 report is + * scr_punycode_warn (scr_async_dyn.c). */ + +enum { + PC_MAX_INT = 2147483647, + PC_BASE = 36, + PC_TMIN = 1, + PC_TMAX = 26, + PC_SKEW = 38, + PC_DAMP = 700, + PC_INITIAL_BIAS = 72, + PC_INITIAL_N = 128 +}; + +static void pc_error(const char *message) { + scr_throw_error_msg(SCR_ERR_RANGE, message, strlen(message)); +} +#define PC_OVERFLOW "Overflow: input needs wider integers to process" +#define PC_NOT_BASIC "Illegal input >= 0x80 (not a basic code point)" +#define PC_INVALID "Invalid input" + +typedef struct { + uint32_t *data; + size_t len, cap; +} PcPoints; + +static void pc_push(PcPoints *p, uint32_t cp) { + if (p->len == p->cap) { + size_t cap = p->cap ? p->cap * 2 : 16; + uint32_t *grown = realloc(p->data, cap * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + p->data = grown; + p->cap = cap; + } + p->data[p->len++] = cp; +} + +/* ucs2decode over WTF-8 storage: a pair is one 4-byte sequence, a lone + * surrogate one 3-byte sequence carrying its own value. */ +static void pc_code_points(const char *s, size_t n, PcPoints *out) { + const unsigned char *b = (const unsigned char *)s; + for (size_t i = 0; i < n;) { + uint32_t c = b[i]; + if (c < 0x80) { + i += 1; + } else if (c < 0xE0 && i + 1 < n) { + c = ((c & 0x1F) << 6) | (b[i + 1] & 0x3F); + i += 2; + } else if (c < 0xF0 && i + 2 < n) { + c = ((c & 0x0F) << 12) | ((uint32_t)(b[i + 1] & 0x3F) << 6) | (b[i + 2] & 0x3F); + i += 3; + } else if (i + 3 < n) { + c = ((c & 0x07) << 18) | ((uint32_t)(b[i + 1] & 0x3F) << 12) | + ((uint32_t)(b[i + 2] & 0x3F) << 6) | (b[i + 3] & 0x3F); + i += 4; + } else { + i += 1; + } + pc_push(out, c); + } +} + +static int64_t pc_adapt(int64_t delta, int64_t points, bool first) { + int64_t k = 0; + delta = first ? delta / PC_DAMP : delta >> 1; + delta += delta / points; + for (; delta > ((PC_BASE - PC_TMIN) * PC_TMAX) >> 1; k += PC_BASE) delta /= PC_BASE - PC_TMIN; + return k + (PC_BASE - PC_TMIN + 1) * delta / (delta + PC_SKEW); +} + +static int64_t pc_basic_to_digit(unsigned char c) { + if (c >= 0x30 && c < 0x3A) return 26 + (c - 0x30); + if (c >= 0x41 && c < 0x5B) return c - 0x41; + if (c >= 0x61 && c < 0x7B) return c - 0x61; + return PC_BASE; +} + +static char pc_digit_to_basic(int64_t digit) { + return (char)(digit + 22 + 75 * (digit < 26)); +} + +/* String.fromCodePoint(...points). */ +static ScrStr *pc_from_points(const PcPoints *p) { + for (size_t i = 0; i < p->len; i++) { + if (p->data[i] > 0x10FFFF) { + char message[48]; + int size = snprintf(message, sizeof message, "Invalid code point %u", (unsigned)p->data[i]); + scr_throw_error_msg(SCR_ERR_RANGE, message, (size_t)size); + return NULL; + } + } + char *out = malloc(p->len * 4 + 1); + if (!out) scr_trap("scriptc: out of memory\n"); + size_t offset = 0; + for (size_t i = 0; i < p->len; i++) { + uint32_t point = p->data[i]; + if (point >= 0xD800 && point <= 0xDBFF && i + 1 < p->len && p->data[i + 1] >= 0xDC00 && + p->data[i + 1] <= 0xDFFF) { + point = 0x10000 + ((point - 0xD800) << 10) + (p->data[i + 1] - 0xDC00); + i++; + } + offset += scr_wtf8_put(out + offset, point); + } + ScrStr *result = scr_str_new(out, offset); + free(out); + return result; +} + +/* punycode.decode over the bytes of one label. Any non-ASCII byte fails + * where its first code unit would: before the last delimiter it is not + * basic, after it it is no digit — so byte positions stand in for code + * unit positions until the first error. */ +static ScrStr *pc_decode(const char *s, size_t n) { + PcPoints output = {0}; + size_t basic = 0; + for (size_t k = n; k > 0; k--) { + if (s[k - 1] == '-') { + basic = k - 1; + break; + } + } + ScrStr *result = NULL; + for (size_t j = 0; j < basic; j++) { + if ((unsigned char)s[j] >= 0x80) { + pc_error(PC_NOT_BASIC); + goto done; + } + pc_push(&output, (unsigned char)s[j]); + } + int64_t i = 0, cp = PC_INITIAL_N, bias = PC_INITIAL_BIAS; + for (size_t index = basic > 0 ? basic + 1 : 0; index < n;) { + int64_t old = i; + for (int64_t w = 1, k = PC_BASE;; k += PC_BASE) { + if (index >= n) { + pc_error(PC_INVALID); + goto done; + } + int64_t digit = pc_basic_to_digit((unsigned char)s[index++]); + if (digit >= PC_BASE) { + pc_error(PC_INVALID); + goto done; + } + if (digit > (PC_MAX_INT - i) / w) { + pc_error(PC_OVERFLOW); + goto done; + } + i += digit * w; + int64_t t = k <= bias ? PC_TMIN : (k >= bias + PC_TMAX ? PC_TMAX : k - bias); + if (digit < t) break; + if (w > PC_MAX_INT / (PC_BASE - t)) { + pc_error(PC_OVERFLOW); + goto done; + } + w *= PC_BASE - t; + } + int64_t out = (int64_t)output.len + 1; + bias = pc_adapt(i - old, out, old == 0); + if (i / out > PC_MAX_INT - cp) { + pc_error(PC_OVERFLOW); + goto done; + } + cp += i / out; + i %= out; + pc_push(&output, 0); + memmove(output.data + i + 1, output.data + i, (output.len - 1 - (size_t)i) * sizeof *output.data); + output.data[i++] = (uint32_t)cp; + } + result = pc_from_points(&output); +done: + free(output.data); + return result; +} + +/* punycode.encode of one label's code points, appended to buf. */ +static bool pc_encode(const PcPoints *input, ScrJsonBuf *buf) { + int64_t cp = PC_INITIAL_N, delta = 0, bias = PC_INITIAL_BIAS; + size_t basic_length = 0; + for (size_t k = 0; k < input->len; k++) { + if (input->data[k] < 0x80) { + scr_jb_putc(buf, (char)input->data[k]); + basic_length++; + } + } + size_t handled = basic_length; + if (basic_length) scr_jb_putc(buf, '-'); + while (handled < input->len) { + int64_t m = PC_MAX_INT; + for (size_t k = 0; k < input->len; k++) + if (input->data[k] >= cp && input->data[k] < m) m = input->data[k]; + int64_t handled_plus_one = (int64_t)handled + 1; + if (m - cp > (PC_MAX_INT - delta) / handled_plus_one) { + pc_error(PC_OVERFLOW); + return false; + } + delta += (m - cp) * handled_plus_one; + cp = m; + for (size_t k = 0; k < input->len; k++) { + int64_t current = input->data[k]; + if (current < cp && ++delta > PC_MAX_INT) { + pc_error(PC_OVERFLOW); + return false; + } + if (current == cp) { + int64_t q = delta; + for (int64_t j = PC_BASE;; j += PC_BASE) { + int64_t t = j <= bias ? PC_TMIN : (j >= bias + PC_TMAX ? PC_TMAX : j - bias); + if (q < t) break; + scr_jb_putc(buf, pc_digit_to_basic(t + (q - t) % (PC_BASE - t))); + q = (q - t) / (PC_BASE - t); + } + scr_jb_putc(buf, pc_digit_to_basic(q)); + bias = pc_adapt(delta, handled_plus_one, handled == basic_length); + delta = 0; + handled++; + } + } + delta++; + cp++; + } + return true; +} + +ScrStr *scr_punycode_decode(const ScrStr *input) { return pc_decode(input->data, input->len); } + +ScrStr *scr_punycode_encode(const ScrStr *input) { + PcPoints points = {0}; + pc_code_points(input->data, input->len, &points); + ScrJsonBuf buf; + scr_jb_init(&buf); + bool ok = pc_encode(&points, &buf); + free(points.data); + ScrStr *result = scr_jb_finish(&buf); + if (ok) return result; + scr_str_release(result); + return NULL; +} + +ScrArr *scr_punycode_ucs2_decode(const ScrStr *input) { + PcPoints points = {0}; + pc_code_points(input->data, input->len, &points); + ScrArr *out = scr_arr_new(SCR_ELEM_F64, points.len); + for (size_t i = 0; i < points.len; i++) scr_arr_push_f64(out, (double)points.data[i]); + free(points.data); + return out; +} + +/* mapDomain: only the part after the first '@' maps (and only up to a + * second one — punycode.js keeps parts[0] and parts[1]); the RFC 3490 + * separators U+002E, U+3002, U+FF0E and U+FF61 all split labels, and the + * labels rejoin with '.'. */ +static ScrStr *pc_map_domain(const ScrStr *input, bool to_ascii) { + const char *s = input->data; + size_t n = input->len; + ScrJsonBuf buf; + scr_jb_init(&buf); + const char *at = memchr(s, '@', n); + if (at) { + scr_jb_write(&buf, s, (size_t)(at - s) + 1); + const char *rest = at + 1; + const char *second = memchr(rest, '@', n - (size_t)(rest - s)); + n = (second ? (size_t)(second - rest) : n - (size_t)(rest - s)); + s = rest; + } + size_t start = 0; + bool ok = true; + for (size_t i = 0; ok && i <= n;) { + size_t sep = 0; + if (i == n) sep = 1; + else if (s[i] == '.') sep = 1; + else if (i + 2 < n && (unsigned char)s[i] == 0xE3 && (unsigned char)s[i + 1] == 0x80 && + (unsigned char)s[i + 2] == 0x82) + sep = 3; + else if (i + 2 < n && (unsigned char)s[i] == 0xEF && + (((unsigned char)s[i + 1] == 0xBC && (unsigned char)s[i + 2] == 0x8E) || + ((unsigned char)s[i + 1] == 0xBD && (unsigned char)s[i + 2] == 0xA1))) + sep = 3; + if (!sep) { + i++; + continue; + } + const char *label = s + start; + size_t len = i - start; + bool ascii = true; + for (size_t k = 0; k < len; k++) ascii = ascii && (unsigned char)label[k] < 0x80; + if (to_ascii && !ascii) { + PcPoints points = {0}; + pc_code_points(label, len, &points); + scr_jb_puts(&buf, "xn--"); + ok = pc_encode(&points, &buf); + free(points.data); + } else if (!to_ascii && len >= 4 && memcmp(label, "xn--", 4) == 0) { + /* string.slice(4).toLowerCase(): ASCII letters fold; the one other + * code point whose lowercase is ASCII is KELVIN SIGN (U+212A → k). + * Any other non-ASCII stays non-ASCII and fails in decode either + * way, at the same position. */ + ScrJsonBuf lower; + scr_jb_init(&lower); + for (size_t k = 4; k < len; k++) { + unsigned char c = (unsigned char)label[k]; + if (c == 0xE2 && k + 2 < len && (unsigned char)label[k + 1] == 0x84 && + (unsigned char)label[k + 2] == 0xAA) { + scr_jb_putc(&lower, 'k'); + k += 2; + } else { + scr_jb_putc(&lower, (char)(c >= 'A' && c <= 'Z' ? c + 32 : c)); + } + } + ScrStr *folded = scr_jb_finish(&lower); + ScrStr *decoded = pc_decode(folded->data, folded->len); + scr_str_release(folded); + if (decoded) { + scr_jb_put_str(&buf, decoded); + scr_str_release(decoded); + } else { + ok = false; + } + } else { + scr_jb_write(&buf, label, len); + } + if (ok && i < n) scr_jb_putc(&buf, '.'); + i += sep; + start = i; + } + ScrStr *result = scr_jb_finish(&buf); + if (ok) return result; + scr_str_release(result); + return NULL; +} + +ScrStr *scr_punycode_to_ascii(const ScrStr *input) { return pc_map_domain(input, true); } +ScrStr *scr_punycode_to_unicode(const ScrStr *input) { return pc_map_domain(input, false); } diff --git a/packages/runtime/src/scr_url.h b/packages/runtime/src/scr_url.h index 134893054..05e5702bb 100644 --- a/packages/runtime/src/scr_url.h +++ b/packages/runtime/src/scr_url.h @@ -53,6 +53,15 @@ ScrDyn *scr_url_legacy_parse(const ScrDyn *url, const ScrDyn *parse_query, const ScrStr *scr_url_legacy_format(const ScrDyn *object, const ScrDyn *options, bool warn); ScrStr *scr_url_legacy_resolve(const ScrDyn *from, const ScrDyn *to, bool warn); ScrStr *scr_url_domain_to_ascii(const ScrDyn *domain, bool missing); +/* node:punycode (scr_url.c): punycode.js 2.1.0's encode/decode, toASCII/ + * toUnicode (mapDomain over RFC 3490 separators) and ucs2.decode (UTF-16 + * code points as an f64 array). Borrowed input; +1, or NULL with the + * module's RangeError pending. */ +ScrStr *scr_punycode_decode(const ScrStr *input); +ScrStr *scr_punycode_encode(const ScrStr *input); +ScrStr *scr_punycode_to_ascii(const ScrStr *input); +ScrStr *scr_punycode_to_unicode(const ScrStr *input); +ScrArr *scr_punycode_ucs2_decode(const ScrStr *input); ScrStr *scr_url_domain_to_unicode(const ScrDyn *domain, bool missing); ScrStr *scr_url_to_path(ScrUrl *u); /* +1, or throws */ ScrStr *scr_url_str_to_path(ScrStr *s); /* +1, or throws */ diff --git a/packages/runtime/src/scr_zlib.c b/packages/runtime/src/scr_zlib.c index b61cdc1cb..ff09e7ea1 100644 --- a/packages/runtime/src/scr_zlib.c +++ b/packages/runtime/src/scr_zlib.c @@ -420,7 +420,22 @@ void scr_zlib_options_drop(ScrZlibOptions *o) { o->dict = NULL; } +static bool scr_zlib_options_parse_object(int mode, const ScrDyn *opts, ScrZlibOptions *out); + +/* A typed options object (a record that crossed into a checked-dynamic + * slot by reference) is read through its materialized view, as a plain + * object is. */ bool scr_zlib_options_parse(int mode, const ScrDyn *opts, ScrZlibOptions *out) { + if (opts == NULL || opts->kind != SCR_DYN_TYPED_REF) return scr_zlib_options_parse_object(mode, opts, out); + ScrDyn *view = scr_dyn_typed_ref_materialize(opts); + bool ok = false; + if (scr_exc_pending()) memset(out, 0, sizeof *out); + else ok = scr_zlib_options_parse_object(mode, view, out); + scr_dyn_release(view); + return ok; +} + +static bool scr_zlib_options_parse_object(int mode, const ScrDyn *opts, ScrZlibOptions *out) { memset(out, 0, sizeof *out); out->window_bits = 15; out->level = Z_DEFAULT_COMPRESSION; diff --git a/packages/runtime/src/scr_zlib_stream.c b/packages/runtime/src/scr_zlib_stream.c index 14d8747e8..4ed717d53 100644 --- a/packages/runtime/src/scr_zlib_stream.c +++ b/packages/runtime/src/scr_zlib_stream.c @@ -431,6 +431,13 @@ static const char *scr_zs_class_name(int mode) { } ScrStream *scr_zlib_stream_new(double mode_d, ScrDyn *opts) { + /* A typed options object is read through its materialized view. */ + if (opts != NULL && opts->kind == SCR_DYN_TYPED_REF) { + ScrDyn *view = scr_dyn_typed_ref_materialize(opts); + ScrStream *stream = scr_exc_pending() ? NULL : scr_zlib_stream_new(mode_d, view); + scr_dyn_release(view); + return stream; + } int mode = (int)mode_d; const ScrDyn *o = opts != NULL && scr_dyn_truthy(opts) ? opts : NULL; ScrZlibOptions zo; diff --git a/packages/runtime/test/test_map.c b/packages/runtime/test/test_map.c index 6eee2d6bb..8899974a8 100644 --- a/packages/runtime/test/test_map.c +++ b/packages/runtime/test/test_map.c @@ -284,6 +284,36 @@ static void test_span_keys(void) { scr_map_release(numbers); } +/* A scratch slice probes like any key; an insertion stores a heap copy, so + * the stored key survives the buffer's next contents. */ +static void test_scratch_keys(void) { + ScrMap *m = scr_map_new(SCR_MAP_KEY_STR, SCR_MAP_VAL_F64, NULL); + ScrStr *line = S("alpha beta alpha"); + _Alignas(16) unsigned char buffer[96]; + ScrStr *alpha = scr_str_slice_scratch(line, 0, 5, false, (ScrStr *)buffer, sizeof buffer); + check(alpha == (ScrStr *)buffer && scr_str_is_scratch(alpha), "short ASCII slice uses the buffer"); + scr_map_set_str_f64(m, alpha, 1); + double out = 0; + check(scr_map_get_str_f64(m, alpha, &out) && out == 1, "scratch key probes its entry"); + ScrStr *beta = scr_str_slice_scratch(line, 6, 10, false, (ScrStr *)buffer, sizeof buffer); + check(!scr_map_has_str(m, beta), "refilled buffer probes its new text"); + scr_map_set_str_f64(m, beta, 2); + ScrStr *again = scr_str_slice_scratch(line, 11, 16, false, (ScrStr *)buffer, sizeof buffer); + scr_map_set_str_f64(m, again, 3); + check(scr_map_size(m) == 2, "scratch overwrite keeps one entry"); + ScrStr *first = scr_map_iter_key_str(m, 0), *second = scr_map_iter_key_str(m, 1); + ScrStr *a = S("alpha"), *b = S("beta"); + check(!scr_str_is_scratch(first) && scr_str_eq(first, a), "stored key is an owned copy"); + check(!scr_str_is_scratch(second) && scr_str_eq(second, b), "second stored key is a copy"); + check(scr_map_get_str_f64(m, a, &out) && out == 3, "overwrite through scratch key"); + scr_str_release(a); + scr_str_release(b); + scr_str_release(first); + scr_str_release(second); + scr_str_release(line); + scr_map_release(m); +} + static void test_same_value_zero(void) { ScrMap *m = scr_map_new(SCR_MAP_KEY_F64, SCR_MAP_VAL_BOOL, NULL); scr_map_set_f64_bool(m, 0.0 / 0.0, true); /* NaN key */ @@ -793,6 +823,7 @@ int main(void) { test_string_entries(); test_same_value_zero(); test_span_keys(); + test_scratch_keys(); test_string_hash_cache(); #ifdef SCR_RC_AUDIT test_rc_accounting(); diff --git a/packages/runtime/test/test_string.c b/packages/runtime/test/test_string.c index 55c49af7e..e49d8eb8b 100644 --- a/packages/runtime/test/test_string.c +++ b/packages/runtime/test/test_string.c @@ -205,6 +205,38 @@ static void surrogate_asserts(void) { } } } + /* Scratch slices hold the materialized text. Only ASCII results of at + * least two units use the caller's buffer, marked as scratch. */ + { + ScrStr *sources[] = {two, s, scr_str_new("GET /api/users 200", 18)}; + const double marks[] = {-INFINITY, -7.5, -3, -0.5, -0.0, 0, 0.5, 1, + 2, 3.9, 5, 9, 40, INFINITY, NAN}; + const size_t nmarks = sizeof marks / sizeof marks[0]; + _Alignas(16) unsigned char buffer[96]; + for (size_t k = 0; k < 3; k++) { + for (size_t a = 0; a < nmarks; a++) { + for (size_t b = 0; b < nmarks; b++) { + for (unsigned substring = 0; substring < 2; substring++) { + ScrStr *src = sources[k]; + ScrStr *scratch = scr_str_slice_scratch(src, marks[a], marks[b], substring != 0, + (ScrStr *)buffer, sizeof buffer); + ScrStr *copy = substring ? scr_str_substring(src, marks[a], marks[b]) + : scr_str_slice(src, marks[a], marks[b]); + bool used = scratch == (ScrStr *)buffer; + bool expected = copy->len >= 2 && scr_bytes_all_ascii(copy->data, copy->len); + total++; + if (scratch->len != copy->len || memcmp(scratch->data, copy->data, copy->len) != 0 || + used != expected || used != scr_str_is_scratch(scratch) || + scr_str_utf16_len(scratch) != scr_str_utf16_len(copy)) + failed++; + scr_str_release(scratch); + scr_str_release(copy); + } + } + } + } + scr_str_release(sources[2]); + } scr_str_release(two); scr_str_release(s); } diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs new file mode 100644 index 000000000..a89fe4650 --- /dev/null +++ b/scripts/zero-cost-compare.mjs @@ -0,0 +1,490 @@ +/* Comparison logic for scripts/zero-cost-gate.mjs, kept free of process + * orchestration so tests/harness/zero-cost-gate.test.ts can pin it. + * + * Artifacts are compared as bytes first. Only when they differ are they split + * into named units (IR functions, LLVM functions, executable symbols) to name + * the first unit that differs. The self-hosted compiler's IR and LLVM exceed + * V8's maximum string length, so splitting works on Buffers and hashes each + * unit instead of decoding the artifact. */ +import { createHash } from "node:crypto"; + +const hash = (bytes) => createHash("sha1").update(bytes).digest("hex"); + +/** Named units of an artifact: `order` lists unit keys in artifact order, + * `hashes` maps each key to a content hash, and `module` holds the + * module-level remainder (IR header, LLVM globals and declarations) as a list + * of comparable pieces with their labels. Repeated names get `#n` suffixes. */ +function unitsBuilder() { + const order = []; + const hashes = new Map(); + const seen = new Map(); + return { + order, + hashes, + module: [], + add(name, bytes) { + const count = seen.get(name) ?? 0; + seen.set(name, count + 1); + const key = count === 0 ? name : `${name}#${count + 1}`; + order.push(key); + hashes.set(key, hash(bytes)); + }, + }; +} + +/* ── IR (compact or readable JSON) ─────────────────────────────────────── */ + +function skipString(bytes, index) { + for (index++; index < bytes.length; index++) { + if (bytes[index] === 0x5c) index++; + else if (bytes[index] === 0x22) return index + 1; + } + return bytes.length; +} + +/** Byte ranges of the root object's `"functions"` array elements, or null. */ +export function irFunctionSpans(bytes) { + const key = Buffer.from('"functions"'); + let depth = 0; + for (let index = 0; index < bytes.length;) { + const byte = bytes[index]; + if (byte === 0x22) { + const end = skipString(bytes, index); + if (depth === 1 && end - index === key.length && bytes.subarray(index, end).equals(key)) { + let next = end; + while (next < bytes.length && bytes[next] !== 0x3a) next++; + next++; + while (next < bytes.length && bytes[next] !== 0x5b && bytes[next] <= 0x20) next++; + if (bytes[next] === 0x5b) return elementSpans(bytes, next); + } + index = end; + continue; + } + if (byte === 0x7b || byte === 0x5b) depth++; + else if (byte === 0x7d || byte === 0x5d) depth--; + index++; + } + return null; +} + +function elementSpans(bytes, open) { + const spans = []; + let depth = 0; + let start = -1; + for (let index = open + 1; index < bytes.length;) { + const byte = bytes[index]; + if (byte === 0x22) { + if (start < 0) start = index; + index = skipString(bytes, index); + continue; + } + if (byte === 0x7b || byte === 0x5b) { + if (start < 0) start = index; + depth++; + } else if (byte === 0x7d || byte === 0x5d) { + if (depth === 0) { + if (start >= 0) spans.push([start, trimEnd(bytes, index)]); + return { open, close: index, spans }; + } + depth--; + } else if (byte === 0x2c && depth === 0) { + spans.push([start, trimEnd(bytes, index)]); + start = -1; + } else if (start < 0 && byte > 0x20) start = index; + index++; + } + return null; +} + +function trimEnd(bytes, end) { + while (end > 0 && bytes[end - 1] <= 0x20) end--; + return end; +} + +/** The `name` of one serialized IR function. Functions serialize `name` + * first; anything else is parsed (a single function is small). */ +function irFunctionName(bytes, [start, end]) { + const head = bytes.toString("utf8", start, Math.min(end, start + 4096)); + const match = /^\{\s*"name"\s*:\s*("(?:[^"\\]|\\.)*")/.exec(head); + if (match) return JSON.parse(match[1]); + const fn = JSON.parse(bytes.toString("utf8", start, end)); + return typeof fn?.name === "string" ? fn.name : ""; +} + +/** IR units: one per function, plus the module header split by top-level + * key (and, for arrays, by element) so header differences are named too. */ +export function irUnits(bytes) { + const units = unitsBuilder(); + const located = irFunctionSpans(bytes); + let header; + if (located === null) header = JSON.parse(bytes.toString("utf8")); + else { + for (const span of located.spans) + units.add(irFunctionName(bytes, span), bytes.subarray(span[0], span[1])); + header = JSON.parse( + bytes.toString("utf8", 0, located.open + 1) + bytes.toString("utf8", located.close), + ); + } + for (const [key, value] of Object.entries(header)) { + if (key === "functions" && located !== null) continue; + if (Array.isArray(value)) + value.forEach((item, index) => { + const name = item && typeof item === "object" && "name" in item ? `:${item.name}` : ""; + units.module.push({ label: `${key}[${index}${name}]`, text: JSON.stringify(item) }); + }); + else units.module.push({ label: key, text: JSON.stringify(value) }); + } + return units; +} + +/* ── LLVM text ─────────────────────────────────────────────────────────── */ + +const DEFINE_NAME = /@("(?:[^"\\]|\\.)*"|[-\w$.]+)\s*\(/; + +/** LLVM units: one per `define … { … }` body; every other line is + * module-level text compared line by line. */ +export function llvmUnits(bytes) { + const units = unitsBuilder(); + let fnStart = -1; + let fnName = ""; + let lineNumber = 0; + for (let start = 0; start < bytes.length;) { + let end = bytes.indexOf(10, start); + if (end < 0) end = bytes.length; + lineNumber++; + if (fnStart >= 0) { + if (end - start === 1 && bytes[start] === 0x7d) { + units.add(fnName, bytes.subarray(fnStart, end)); + fnStart = -1; + } + } else if (bytes[start] === 0x64 && bytes.toString("latin1", start, start + 7) === "define ") { + const line = bytes.toString("utf8", start, end); + fnName = DEFINE_NAME.exec(line)?.[1] ?? ``; + fnStart = start; + if (line.endsWith("}")) { + units.add(fnName, bytes.subarray(fnStart, end)); + fnStart = -1; + } + } else if (end > start) { + units.module.push({ label: `line ${lineNumber}`, text: bytes.toString("utf8", start, end) }); + } + start = end + 1; + } + return units; +} + +/* ── unit comparison ───────────────────────────────────────────────────── */ + +const clip = (text, width = 160) => (text.length > width ? `${text.slice(0, width)}…` : text); + +/** Compare two unit sets. `first` names the first difference in baseline + * order: a changed or removed function, then an added one, then the first + * differing module-level piece. */ +export function compareUnits(baseline, candidate) { + const changed = []; + const removed = []; + for (const key of baseline.order) { + const other = candidate.hashes.get(key); + if (other === undefined) removed.push(key); + else if (other !== baseline.hashes.get(key)) changed.push(key); + } + const added = candidate.order.filter((key) => !baseline.hashes.has(key)); + let module = null; + const count = Math.max(baseline.module.length, candidate.module.length); + for (let index = 0; index < count && module === null; index++) { + const a = baseline.module[index]; + const b = candidate.module[index]; + if (a?.text !== b?.text) + module = { + label: (a ?? b).label, + baseline: a === undefined ? null : clip(a.text), + candidate: b === undefined ? null : clip(b.text), + }; + } + const firstFunction = baseline.order.find( + (key) => !candidate.hashes.has(key) || candidate.hashes.get(key) !== baseline.hashes.get(key), + ); + let first = null; + if (firstFunction !== undefined) + first = `${changed.includes(firstFunction) ? "changed" : "removed"} ${firstFunction}`; + else if (added.length > 0) first = `added ${added[0]}`; + else if (module !== null) first = `module ${module.label}`; + else if (baseline.order.join("\n") !== candidate.order.join("\n")) first = "function order"; + return { + identical: first === null, + first, + changed, + added, + removed, + module, + functions: { baseline: baseline.order.length, candidate: candidate.order.length }, + }; +} + +/** Byte-compare an artifact and, when it differs, name what changed. */ +export function compareArtifact(kind, baseline, candidate) { + if (baseline.equals(candidate)) return { identical: true }; + const split = kind === "ir" ? irUnits : llvmUnits; + const result = compareUnits(split(baseline), split(candidate)); + // Equal units with unequal bytes means formatting outside the units. + return { ...result, identical: false, first: result.first ?? "bytes outside named units" }; +} + +/* ── executables ───────────────────────────────────────────────────────── */ + +/** The output of `nm` for `binary`, from its spawnSync result. Throws when + * nm could not start, was killed by a signal, or exited with a non-zero + * status, because the symbol checks would otherwise read an empty list. */ +export function nmOutput(binary, result) { + if (result.error) throw new Error(`nm ${binary}: ${result.error.message}`); + if (result.signal) throw new Error(`nm ${binary}: killed by ${result.signal}`); + if (result.status !== 0) { + const detail = typeof result.stderr === "string" ? result.stderr.trim() : ""; + throw new Error( + `nm ${binary}: exited with status ${result.status}${detail ? `: ${detail}` : ""}`, + ); + } + return result.stdout; +} + +/** Defined symbols from `nm -S` output (GNU/LLVM nm, address size type + * name) or `nm` output without sizes (Apple, address type name). */ +export function parseNm(text) { + const symbols = []; + for (const line of text.split("\n")) { + const fields = line.trim().split(/\s+/); + if (fields.length === 4 && /^[0-9a-f]+$/i.test(fields[1])) + symbols.push({ + address: BigInt(`0x${fields[0]}`), + size: BigInt(`0x${fields[1]}`), + type: fields[2], + name: fields[3], + }); + else if (fields.length === 3 && /^[0-9a-f]+$/i.test(fields[0])) + symbols.push({ + address: BigInt(`0x${fields[0]}`), + size: null, + type: fields[1], + name: fields[2], + }); + } + return symbols; +} + +/** The virtual address of file offset `offset` in an ELF64 little-endian + * executable, from its PT_LOAD program headers, or null. */ +export function elfOffsetToAddress(bytes, offset) { + if (bytes.length < 64 || bytes.readUInt32BE(0) !== 0x7f454c46 || bytes[4] !== 2) return null; + const phoff = Number(bytes.readBigUInt64LE(0x20)); + const phentsize = bytes.readUInt16LE(0x36); + const phnum = bytes.readUInt16LE(0x38); + for (let index = 0; index < phnum; index++) { + const at = phoff + index * phentsize; + if (bytes.readUInt32LE(at) !== 1) continue; + const fileOffset = bytes.readBigUInt64LE(at + 8); + const address = bytes.readBigUInt64LE(at + 16); + const fileSize = bytes.readBigUInt64LE(at + 32); + const target = BigInt(offset); + if (target >= fileOffset && target < fileOffset + fileSize) + return address + (target - fileOffset); + } + return null; +} + +/** Name what differs between two executables: symbols added, removed or + * resized, then the symbol holding the first differing byte. */ +export function compareExecutables(baseline, candidate, baselineNm, candidateNm) { + if (baseline.equals(candidate)) return { identical: true }; + const before = new Map(parseNm(baselineNm).map((s) => [s.name, s])); + const after = new Map(parseNm(candidateNm).map((s) => [s.name, s])); + const removed = [...before.keys()].filter((name) => !after.has(name)); + const added = [...after.keys()].filter((name) => !before.has(name)); + const resized = [...before.values()] + .filter((s) => after.has(s.name) && s.size !== null && after.get(s.name).size !== s.size) + .map((s) => ({ + name: s.name, + baseline: Number(s.size), + candidate: Number(after.get(s.name).size), + })); + let offset = 0; + const limit = Math.min(baseline.length, candidate.length); + while (offset < limit && baseline[offset] === candidate[offset]) offset++; + const address = elfOffsetToAddress(baseline, offset); + let holder = null; + if (address !== null) + for (const s of before.values()) + if (s.size !== null && address >= s.address && address < s.address + s.size) holder = s.name; + const first = + resized[0] !== undefined + ? `resized ${resized[0].name}` + : removed[0] !== undefined + ? `removed ${removed[0]}` + : added[0] !== undefined + ? `added ${added[0]}` + : holder !== null + ? `bytes in ${holder}` + : `byte offset ${offset}`; + return { identical: false, first, offset, holder, added, removed, resized }; +} + +/* ── dynamic-tier symbols (two-tier design, invariant I3) ────────────────── */ + +/** Runtime symbol prefixes that belong to the dynamic tier: code a fully + * typed program has no reason to reach. A typed program's release binary + * must define none of them. Extend this list as dynamic-tier units land + * (S3/S4 add their scr_js_* families) and as legacy ScrDyn families move. + * The ScrDyn prefixes are the object-model entry points the two-tier audit + * (results/design-two-tier/DESIGN.md §3.2-3.3) lists as dynamic-only. */ +export const DYNAMIC_TIER_PREFIXES = [ + // Dynamic-tier runtime units (scr_js_value, scr_js_object, ...), from S3. + "scr_js_", + // ScrDyn property bags: reads, writes, lookup and key indexing. + "scr_dyn_obj_", + "scr_dyn_find_", + "scr_dyn_index_", + "scr_dyn_key_", + "sc_dyn_key_", + // Descriptors, own-property reflection, prototypes and proxies. + "scr_dyn_property_", + "scr_dyn_define_", + "scr_dyn_descriptor_", + "scr_dyn_own_", + "scr_dyn_get_own_", + "scr_dyn_prototype", + "scr_dyn_proxy_", + // Dynamic calls and the dynamic receiver read. + "scr_dyn_call", + "scr_dyn_apply", + "scr_dyn_invoke", + "scr_dyn_construct", + "scr_dyn_function_apply", + "scr_dyn_fn_get", + "scr_dyn_this_get", + // Dynamic operators and coercions. + "scr_dyn_add_", + "scr_dyn_arithmetic", + "scr_dyn_to_numeric", + "scr_dyn_number_coerce", + "scr_dyn_string_coerce", +]; + +/** Symbols under those prefixes that typed binaries reach today through a + * leak the two-tier design schedules for removal (S1). Remove an entry when + * its leak closes. */ +export const DYNAMIC_TIER_EXEMPT = new Map([ + // §3.3 L10: scr_dyn_release's dispose switch, linked whenever any dyn + // value can be released. + ["scr_dyn_obj_drop_keys", "L10"], +]); + +/** The dynamic-tier symbols among `names`, sorted. */ +export function dynamicTierSymbols(names) { + return [...new Set(names)] + .filter( + (name) => + DYNAMIC_TIER_PREFIXES.some((prefix) => name.startsWith(prefix)) && + !DYNAMIC_TIER_EXEMPT.has(name), + ) + .sort(); +} + +/* ── cachegrind ────────────────────────────────────────────────────────── */ + +/** Background threads whose instruction counts depend on scheduling: the + * allocator's prefault thread. */ +export const CACHEGRIND_EXCLUDED = new Set(["scr_sa_pf_main", "scr_sa_prefault"]); + +/** Per-function instruction counts (Ir) from a cachegrind output file, + * restricted to `keep` (the executable's own defined symbols). Dynamic + * linker and libc/pthread functions are outside `keep`; their counts vary + * run to run with lazy binding and thread wakeups. */ +export function cachegrindCounts(text, keep) { + const counts = new Map(); + let events = null; + let current = null; + for (const line of text.split("\n")) { + if (line.startsWith("events:")) events = line.slice(7).trim().split(/\s+/); + else if (line.startsWith("fn=")) { + const name = line.slice(3).trim(); + current = keep.has(name) && !CACHEGRIND_EXCLUDED.has(name) ? name : null; + } else if (line.startsWith("fl=") || line.startsWith("fi=") || line.startsWith("fe=")) continue; + else if (current !== null && /^\d/.test(line)) { + const fields = line.trim().split(/\s+/); + const column = events === null ? 0 : Math.max(0, events.indexOf("Ir")); + const ir = Number(fields[column + 1] ?? 0); + counts.set(current, (counts.get(current) ?? 0) + ir); + } + } + return counts; +} + +/** Per-function instruction-count deltas, largest absolute first. */ +export function compareCounts(baseline, candidate) { + const names = new Set([...baseline.keys(), ...candidate.keys()]); + const deltas = []; + let totalBaseline = 0; + let totalCandidate = 0; + for (const name of names) { + const a = baseline.get(name) ?? 0; + const b = candidate.get(name) ?? 0; + totalBaseline += a; + totalCandidate += b; + if (a !== b) deltas.push({ name, baseline: a, candidate: b, delta: b - a }); + } + deltas.sort((x, y) => Math.abs(y.delta) - Math.abs(x.delta) || (x.name < y.name ? -1 : 1)); + return { + identical: deltas.length === 0, + total: { baseline: totalBaseline, candidate: totalCandidate }, + deltas, + }; +} + +/* ── verdicts ──────────────────────────────────────────────────────────── */ + +/** One program's verdict from its build results and artifact comparisons. + * `allowed` marks programs that match the change's pattern signature. */ +export function programVerdict({ baseline, candidate, artifacts, allowed }) { + if (!baseline.ok || !candidate.ok) { + if (!baseline.ok && !candidate.ok && baseline.error === candidate.error) + return { verdict: "both-fail", fails: false }; + return { verdict: "build-error", fails: true }; + } + const differing = Object.entries(artifacts).filter(([, r]) => !r.identical); + if (differing.length === 0) return { verdict: "identical", fails: false }; + return allowed ? { verdict: "allowed", fails: false } : { verdict: "differs", fails: true }; +} + +export function median(values) { + const sorted = [...values].sort((a, b) => a - b); + const middle = sorted.length >> 1; + return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2; +} + +export function geomean(values) { + return values.length === 0 + ? 1 + : Math.exp(values.reduce((sum, value) => sum + Math.log(value), 0) / values.length); +} + +/** Compile-time ratio (candidate / baseline) for a set of programs. Each + * row holds paired samples: one baseline and one candidate build that ran + * side by side, so both saw the same machine load. The estimate is the + * geometric mean of each program's median pair ratio, with a 95% bootstrap + * interval over the pairs. Builds on a shared VM vary by 10-20% run to run, + * so the gate judges the interval, not the point estimate. */ +export function timeRatio(rows, resamples = 2000) { + let state = 0x5eed; + const random = () => { + state = (Math.imul(state, 1664525) + 1013904223) >>> 0; + return state / 4294967296; + }; + const pick = (xs) => + Array.from({ length: xs.length }, () => xs[Math.floor(random() * xs.length)]); + const estimate = (draw) => geomean(rows.map((r) => median(draw(r.ratios)))); + const ratios = Array.from({ length: resamples }, () => estimate(pick)).sort((a, b) => a - b); + return { + ratio: estimate((xs) => xs), + low: ratios[Math.floor(resamples * 0.025)], + high: ratios[Math.floor(resamples * 0.975)], + }; +} diff --git a/scripts/zero-cost-gate.mjs b/scripts/zero-cost-gate.mjs new file mode 100644 index 000000000..f4272ac6a --- /dev/null +++ b/scripts/zero-cost-gate.mjs @@ -0,0 +1,652 @@ +/* Zero-cost gate: prove that a compiler change leaves good code untouched. + * + * Compiles a fixed good-code set with a baseline and a candidate checkout and + * compares IR, LLVM and release executables byte for byte, plus compile time + * and binary size, and optionally per-function cachegrind instruction counts. + * Both checkouts compile the same program files (from --programs, default the + * baseline), so program paths embedded in artifacts match; checkout paths and + * timestamps do not reach the artifacts. Builds bypass the native cache. + * + * The good-code set: the runtime benchmark workloads (and benchmarks/builds + * applications), the self-hosted compiler (IR and LLVM), and the TypeScript + * corpus programs. A program whose sources match --allow (the change's + * pattern signature) may differ; any other difference fails the gate. */ +import assert from "node:assert/strict"; +import { spawn, spawnSync } from "node:child_process"; +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + readdirSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; +import { availableParallelism } from "node:os"; +import { dirname, join, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; +import { + cachegrindCounts, + compareArtifact, + compareCounts, + compareExecutables, + dynamicTierSymbols, + median, + nmOutput, + parseNm, + programVerdict, + timeRatio, +} from "./zero-cost-compare.mjs"; + +const repoRoot = fileURLToPath(new URL("..", import.meta.url)); +const { values } = parseArgs({ + options: { + baseline: { type: "string" }, + candidate: { type: "string", default: repoRoot }, + programs: { type: "string", default: "baseline" }, + set: { type: "string", default: "bench,self,corpus" }, + quick: { type: "boolean", default: false }, + corpus: { type: "string", default: "ts" }, + shard: { type: "string" }, + only: { type: "string" }, + allow: { type: "string" }, + "timing-runs": { type: "string" }, + "self-timing-runs": { type: "string", default: "0" }, + "max-time-growth": { type: "string", default: "2" }, + "max-size-growth": { type: "string", default: "0" }, + cachegrind: { type: "boolean", default: false }, + jobs: { type: "string", default: String(Math.max(1, availableParallelism())) }, + json: { type: "string" }, + keep: { type: "boolean", default: false }, + help: { type: "boolean", default: false }, + }, +}); +if (values.help || values.baseline === undefined) { + console.log(`Usage: node scripts/zero-cost-gate.mjs --baseline= [--candidate=] + [--programs=baseline|candidate|

] [--set=bench,self,corpus] [--quick] + [--corpus=ts|all] [--shard=i/n] [--only=] [--allow=] + [--timing-runs=N] [--self-timing-runs=N] [--max-time-growth=2] + [--max-size-growth=0] + [--cachegrind] [--jobs=N] [--json=] [--keep] + +Each checkout must be built (packages/cli/dist/bootstrap.js and this host's +runtime pack). Both compile the same program files, taken from --programs +(default: the baseline checkout). + +--set picks the good-code set: bench (benchmarks/runtime workloads and the +benchmarks/builds applications), self (packages/compiler/src/native/cli.ts, +LLVM only), corpus (tests/corpus TypeScript programs; --corpus=all adds +JavaScript entries). --quick is --set=bench --timing-runs=0, for CI. +--shard=i/n keeps every n-th corpus program; --only filters program ids. + +--allow= is the change's pattern signature: programs whose source +files match it may differ (reported as "allowed"). Any other difference in +IR, LLVM or the release executable fails the gate. + +--timing-runs=N (default 5) times N paired --emit=llvm builds per bench +program, baseline and candidate side by side. The self-hosted compiler's +comparison build is its first pair; --self-timing-runs adds more. The gate +fails when the 95% bootstrap interval of the bench geomean ratio, or of the +self-hosted ratio, lies entirely above --max-time-growth percent; an interval +that straddles it is reported as inconclusive. +--max-size-growth bounds executable growth (percent) of allowed programs. + +Dynamic-tier symbols (two-tier invariant I3): no executable may gain +symbols under DYNAMIC_TIER_PREFIXES (scripts/zero-cost-compare.mjs) unless +it matches --allow, and fully typed bench workloads (all but +DYNAMIC_WORKLOADS in this script) may define none. + +--cachegrind (Linux, valgrind on PATH) runs differing executables under +cachegrind (bench workloads with their arguments, corpus programs without) +and compares instruction counts per function of the executable's own +symbols, excluding the allocator's prefault thread. + +Exit status: 0 when the gate passes, 1 when it fails.`); + process.exit(values.help ? 0 : 2); +} + +const trees = { baseline: resolve(values.baseline), candidate: resolve(values.candidate) }; +for (const [label, root] of Object.entries(trees)) + assert.ok( + existsSync(join(root, "packages/cli/dist/bootstrap.js")), + `${label} checkout ${root} has no built CLI (packages/cli/dist/bootstrap.js)`, + ); +const programsRoot = + values.programs === "baseline" || values.programs === "candidate" + ? trees[values.programs] + : resolve(values.programs); +const sets = new Set((values.quick ? "bench" : values.set).split(",")); +for (const set of sets) assert.ok(["bench", "self", "corpus"].includes(set), `unknown set ${set}`); +const timingRuns = Number(values["timing-runs"] ?? (values.quick ? 0 : 5)); +const selfTimingRuns = Number(values["self-timing-runs"]); +const maxTimeGrowth = Number(values["max-time-growth"]) / 100; +const maxSizeGrowth = Number(values["max-size-growth"]) / 100; +const jobs = Number(values.jobs); +assert.ok(Number.isInteger(timingRuns) && timingRuns >= 0, "--timing-runs must be >= 0"); +assert.ok(Number.isInteger(jobs) && jobs >= 1, "--jobs must be >= 1"); +assert.ok(["ts", "all"].includes(values.corpus), "--corpus must be ts or all"); +const only = values.only ? new RegExp(values.only) : null; +const allow = values.allow ? new RegExp(values.allow, "m") : null; +if (values.cachegrind) + assert.equal( + spawnSync("valgrind", ["--version"]).status, + 0, + "--cachegrind needs valgrind on PATH", + ); + +const work = mkdtempSync("/tmp/scriptc-zero-cost-"); + +/* ── the good-code set ─────────────────────────────────────────────────── */ + +/** Every file a program reads: the entry and its relative imports. */ +function programInputs(entry) { + const files = new Set(); + const pending = [entry]; + while (pending.length > 0) { + const file = pending.pop(); + if (files.has(file)) continue; + files.add(file); + const text = readFileSync(file, "utf8"); + for (const match of text.matchAll( + /(?:from|import|require)\s*\(?\s*["'](\.{1,2}\/[^"']+)["']/g, + )) { + const base = resolve(dirname(file), match[1]); + const stem = base.replace(/\.(m|c)?js$/, ""); + const found = [ + base, + `${stem}.ts`, + `${stem}.mts`, + `${stem}.cts`, + `${base}.ts`, + join(base, "index.ts"), + ].find((candidate) => existsSync(candidate) && statSync(candidate).isFile()); + if (found !== undefined) pending.push(found); + } + } + return [...files]; +} + +function goodCodeSet() { + const programs = []; + if (sets.has("bench")) { + const suite = join(programsRoot, "benchmarks/runtime"); + const manifest = JSON.parse(readFileSync(join(suite, "workloads.json"), "utf8")); + const seen = new Set(); + for (const w of manifest.workloads) { + const entry = join(suite, w.entry); + if (seen.has(entry)) continue; + seen.add(entry); + programs.push({ id: `bench/${w.name}`, entry, kind: "exe", workload: w }); + } + } + if (sets.has("self")) + programs.push({ + id: "self/cli", + entry: join(programsRoot, "packages/compiler/src/native/cli.ts"), + kind: "source", + }); + if (sets.has("corpus")) { + const corpus = join(programsRoot, "tests/corpus"); + const exts = values.corpus === "all" ? ["ts", "js", "mjs", "cjs"] : ["ts"]; + const entries = []; + for (const name of readdirSync(corpus).sort()) { + const path = join(corpus, name); + if (statSync(path).isDirectory()) { + const main = exts.map((ext) => join(path, `main.${ext}`)).find((p) => existsSync(p)); + if (main !== undefined) entries.push(main); + } else if (exts.some((ext) => name.endsWith(`.${ext}`))) entries.push(path); + } + let selected = entries; + if (values.shard) { + const [index, count] = values.shard.split("/").map(Number); + assert.ok(index >= 1 && index <= count, "--shard must be i/n with 1 <= i <= n"); + selected = entries.filter((_, k) => k % count === index - 1); + } + for (const entry of selected) { + const head = readFileSync(entry, "utf8").split("\n", 2); + const speed = head.some((line) => /^\/\/ @optimization:\s*speed\s*$/.test(line)); + programs.push({ + id: `corpus/${relative(corpus, entry)}`, + entry, + kind: "exe", + optimization: speed ? "speed" : "release", + }); + } + } + return programs.filter((p) => only === null || only.test(p.id)); +} + +/* ── builds ────────────────────────────────────────────────────────────── */ + +function exec(command, args, options = {}) { + return new Promise((resolvePromise) => { + const started = process.hrtime.bigint(); + const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"], ...options }); + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8").on("data", (c) => (stdout += c)); + child.stderr.setEncoding("utf8").on("data", (c) => (stderr += c)); + child.on("close", (status, signal) => + resolvePromise({ + status, + signal, + stdout, + stderr, + ms: Number(process.hrtime.bigint() - started) / 1e6, + }), + ); + }); +} + +function buildEnv() { + const env = { ...process.env, SCRIPTC_NO_CACHE: "1" }; + delete env.SCRIPTC_TIMING; + delete env.SCRIPTC_CACHE_DIR; + return env; +} + +function compile(tree, args) { + return exec( + process.execPath, + ["--max-old-space-size=8192", join(tree, "packages/cli/dist/bootstrap.js"), "build", ...args], + { cwd: programsRoot, env: buildEnv() }, + ); +} + +/** Build one program with one checkout. Executable programs build once, + * keeping the LLVM and IR beside the release executable. The self-hosted + * compiler emits LLVM only: each of its frontend runs takes minutes, and its + * LLVM names the first differing function as well as its IR would. */ +async function buildProgram(program, label) { + const dir = join(work, label, program.id.replace(/[^\w.-]+/g, "_")); + mkdirSync(dir, { recursive: true }); + const tree = trees[label]; + const failure = (result) => ({ + ok: false, + error: (result.stderr || result.stdout || `exit ${result.status ?? result.signal}`) + .replaceAll(dir, "") + .trim() + .slice(0, 4000), + }); + if (program.kind === "source") { + const llvm = join(dir, "program.ll"); + const result = await compile(tree, [program.entry, "--emit=llvm", "-o", llvm]); + if (result.status !== 0) return failure(result); + return { ok: true, dir, llvm, ms: result.ms }; + } + const exe = join(dir, "program"); + const result = await compile(tree, [ + program.entry, + `--optimization=${program.optimization ?? "release"}`, + "--keep-llvm", + "--emit-ir", + "-o", + exe, + ]); + if (result.status !== 0) return failure(result); + const files = readdirSync(dir); + const pick = (suffix) => { + const name = files.find((f) => f.endsWith(suffix)); + return name === undefined ? null : join(dir, name); + }; + return { ok: true, dir, exe, ir: pick(".ir.json"), llvm: pick(".ll"), ms: result.ms }; +} + +const nmCache = new Map(); +function nm(binary) { + if (nmCache.has(binary)) return nmCache.get(binary); + const result = spawnSync("nm", process.platform === "darwin" ? [binary] : ["-S", binary], { + encoding: "utf8", + maxBuffer: 256 * 1024 * 1024, + }); + const text = nmOutput(binary, result); + nmCache.set(binary, text); + return text; +} + +/** Names of the functions an executable defines. */ +const definedFunctions = (binary) => + parseNm(nm(binary)) + .filter((s) => /^[tTwW]$/.test(s.type)) + .map((s) => s.name); + +/* Bench workloads whose release binaries link dynamic-tier symbols on + * 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. + * Remove a workload here when a leak fix makes its binary clean. */ +const DYNAMIC_WORKLOADS = new Set([ + "bench/json-records", + "bench/cli-config", + "bench/http-api", + "bench/validate-errors", + "bench/regex-logs", + "bench/atomics-contended", + "bench/intern-table", + "bench/typed-array-scan-workers", + "bench/log-summary", + "bench/inventory-report", +]); + +/** The I3 symbol check. Every executable may not gain dynamic-tier symbols + * over the baseline (unless the program matches --allow); a fully typed + * bench workload may contain none at all. */ +function dynamicTierCheck(program, baseline, candidate, artifacts, allowed) { + if (!baseline.exe || !artifacts.exe) return null; + const typed = program.id.startsWith("bench/") && !DYNAMIC_WORKLOADS.has(program.id); + if (artifacts.exe.identical && !typed) return null; + const after = dynamicTierSymbols(definedFunctions(candidate.exe)); + const before = artifacts.exe.identical + ? after + : dynamicTierSymbols(definedFunctions(baseline.exe)); + const added = after.filter((name) => !before.includes(name)); + const failures = []; + if (typed && after.length > 0) + failures.push( + `fully typed program links dynamic-tier symbols: ${after.slice(0, 5).join(", ")}`, + ); + if (added.length > 0 && !allowed) + failures.push(`gained dynamic-tier symbols: ${added.slice(0, 5).join(", ")}`); + return { typed, count: after.length, added, failures }; +} + +function compareBuilds(baseline, candidate) { + const artifacts = {}; + for (const kind of ["ir", "llvm"]) { + if (!(kind in baseline)) continue; + if (baseline[kind] === null || candidate[kind] === null) { + artifacts[kind] = { identical: false, first: `missing ${kind} artifact` }; + continue; + } + artifacts[kind] = compareArtifact( + kind, + readFileSync(baseline[kind]), + readFileSync(candidate[kind]), + ); + } + if (baseline.exe) { + const a = readFileSync(baseline.exe); + const b = readFileSync(candidate.exe); + artifacts.exe = a.equals(b) + ? { identical: true } + : compareExecutables(a, b, nm(baseline.exe), nm(candidate.exe)); + artifacts.exe.bytes = { baseline: a.length, candidate: b.length }; + } + return artifacts; +} + +/** Run tasks with at most `jobs` at a time; heavy tasks run alone. */ +async function pool(tasks) { + const results = new Array(tasks.length); + let next = 0; + let running = 0; + let heavy = false; + await new Promise((done) => { + const pump = () => { + if (next === tasks.length && running === 0) return done(); + while (next < tasks.length && running < jobs && !heavy) { + const task = tasks[next]; + if (task.heavy && running > 0) break; + const index = next++; + running++; + if (task.heavy) heavy = true; + task.run().then((value) => { + results[index] = value; + running--; + if (task.heavy) heavy = false; + pump(); + }); + } + }; + pump(); + }); + return results; +} + +/* ── cachegrind ────────────────────────────────────────────────────────── */ + +async function instructionCounts(binary, args, out) { + const result = await exec("valgrind", [ + "--tool=cachegrind", + "--cache-sim=no", + `--cachegrind-out-file=${out}`, + binary, + ...args, + ]); + // Corpus programs may exit non-zero on purpose (`// @exit: 1`). + if (result.signal !== null || !existsSync(out)) return null; + return cachegrindCounts(readFileSync(out, "utf8"), new Set(definedFunctions(binary))); +} + +/* ── main ──────────────────────────────────────────────────────────────── */ + +function revision(root) { + const r = spawnSync("git", ["-C", root, "rev-parse", "--short", "HEAD"], { encoding: "utf8" }); + if (r.status !== 0) return null; + const dirty = spawnSync("git", ["-C", root, "status", "--porcelain", "--untracked-files=no"], { + encoding: "utf8", + }); + return r.stdout.trim() + (dirty.stdout.trim() ? "+dirty" : ""); +} + +const fmtMs = (ms) => `${(ms / 1000).toFixed(2)}s`; +const pct = (ratio) => `${ratio >= 1 ? "+" : ""}${((ratio - 1) * 100).toFixed(2)}%`; + +const programs = goodCodeSet(); +assert.ok(programs.length > 0, "the selected good-code set is empty"); +const report = { + schema: 1, + date: new Date().toISOString(), + host: { platform: process.platform, arch: process.arch, node: process.version }, + trees: Object.fromEntries( + Object.entries(trees).map(([label, root]) => [label, { root, revision: revision(root) }]), + ), + programsRoot, + allow: values.allow ?? null, + programs: [], + timing: null, + failures: [], +}; +console.log( + `zero-cost gate: ${programs.length} programs (${[...sets].join(", ")}), ` + + `baseline ${trees.baseline}, candidate ${trees.candidate}, programs from ${programsRoot}`, +); + +let failed = false; +const started = Date.now(); +await pool( + programs.map((program) => ({ + heavy: program.kind === "source", + run: async () => { + const [baseline, candidate] = await Promise.all([ + buildProgram(program, "baseline"), + buildProgram(program, "candidate"), + ]); + const allowed = + allow !== null && + programInputs(program.entry).some((f) => allow.test(readFileSync(f, "utf8"))); + const artifacts = baseline.ok && candidate.ok ? compareBuilds(baseline, candidate) : {}; + const { verdict, fails } = programVerdict({ baseline, candidate, artifacts, allowed }); + const entry = { + id: program.id, + verdict, + allowed, + buildMs: { baseline: baseline.ms ?? null, candidate: candidate.ms ?? null }, + artifacts, + }; + if (!baseline.ok) entry.baselineError = baseline.error; + if (!candidate.ok) entry.candidateError = candidate.error; + const size = artifacts.exe?.bytes; + if (verdict === "allowed" && size && size.candidate > size.baseline * (1 + maxSizeGrowth)) { + entry.sizeFailure = true; + report.failures.push( + `${program.id}: executable grew ${size.baseline} -> ${size.candidate} bytes`, + ); + } + if (fails) report.failures.push(`${program.id}: ${verdict}`); + const symbols = + baseline.ok && candidate.ok + ? dynamicTierCheck(program, baseline, candidate, artifacts, allowed) + : null; + if (symbols !== null) entry.dynamicTier = symbols; + for (const failure of symbols?.failures ?? []) + report.failures.push(`${program.id}: ${failure}`); + if (fails || entry.sizeFailure || symbols?.failures.length) failed = true; + if (values.cachegrind && artifacts.exe && !artifacts.exe.identical) { + // Bench workloads run with their benchmark arguments; corpus + // programs take none. + const w = program.workload ?? { args: [] }; + if (w.kind === "server" || w.args.includes("{input}")) + entry.cachegrind = { skipped: "needs a server client or generated input" }; + else { + const counts = await Promise.all([ + instructionCounts(baseline.exe, w.args, join(baseline.dir, "cg.out")), + instructionCounts(candidate.exe, w.args, join(candidate.dir, "cg.out")), + ]); + entry.cachegrind = counts.includes(null) + ? { skipped: "valgrind run failed" } + : (({ deltas, ...rest }) => ({ ...rest, deltas: deltas.slice(0, 20) }))( + compareCounts(counts[0], counts[1]), + ); + } + } + printProgram(entry); + report.programs.push(entry); + nmCache.delete(baseline.exe); + nmCache.delete(candidate.exe); + if (!values.keep) { + if (baseline.dir) rmSync(baseline.dir, { recursive: true, force: true }); + if (candidate.dir) rmSync(candidate.dir, { recursive: true, force: true }); + } + }, + })), +); + +function printProgram(entry) { + const label = { + identical: "identical", + allowed: "allowed ", + differs: "DIFFERS ", + "build-error": "ERROR ", + "both-fail": "both-fail", + }[entry.verdict]; + const notes = []; + for (const [kind, result] of Object.entries(entry.artifacts)) + if (!result.identical) { + const counts = result.changed + ? ` (${result.changed.length} changed, ${result.added.length} added, ${result.removed.length} removed)` + : ""; + notes.push(`${kind}: ${result.first}${counts}`); + } + const size = entry.artifacts.exe?.bytes; + if (size && size.baseline !== size.candidate) + notes.push( + `size ${size.baseline} -> ${size.candidate} B (${pct(size.candidate / size.baseline)})`, + ); + if (entry.cachegrind?.total) { + const { total, deltas } = entry.cachegrind; + notes.push( + `Ir ${total.baseline} -> ${total.candidate} (${pct(total.candidate / Math.max(1, total.baseline))})` + + (deltas[0] + ? `, largest ${deltas[0].name} ${deltas[0].delta > 0 ? "+" : ""}${deltas[0].delta}` + : ""), + ); + } else if (entry.cachegrind?.skipped) + notes.push(`cachegrind skipped: ${entry.cachegrind.skipped}`); + for (const failure of entry.dynamicTier?.failures ?? []) notes.push(`SYMBOLS: ${failure}`); + if (entry.baselineError) notes.push(`baseline: ${entry.baselineError.split("\n")[0]}`); + if (entry.candidateError) notes.push(`candidate: ${entry.candidateError.split("\n")[0]}`); + console.log(`${label} ${entry.id}${notes.length ? ` ${notes.join("; ")}` : ""}`); +} + +/* Compile time: paired --emit=llvm builds, the baseline and the candidate + * side by side, one program at a time. Identical LLVM means everything after + * emission (LLVM optimization, linking) costs the same, so the timed part is + * the compiler's own work. The self-hosted compiler takes minutes per build; + * its main comparison build is its first pair. */ +if (timingRuns > 0) { + const rows = []; + const pair = async (program) => { + const [baseline, candidate] = await Promise.all( + ["baseline", "candidate"].map((label) => + compile(trees[label], [ + program.entry, + "--emit=llvm", + "-o", + join(work, `timing-${label}.ll`), + ]), + ), + ); + return baseline.status === 0 && candidate.status === 0 ? [baseline.ms, candidate.ms] : null; + }; + for (const program of programs) { + if (program.id.startsWith("corpus/")) continue; + const row = { id: program.id, baselineMs: [], candidateMs: [], ratios: [] }; + const add = (sample) => { + if (sample === null) return; + row.baselineMs.push(Math.round(sample[0])); + row.candidateMs.push(Math.round(sample[1])); + row.ratios.push(sample[1] / sample[0]); + }; + const runs = program.kind === "source" ? selfTimingRuns : timingRuns; + if (program.kind === "source") { + const built = report.programs.find((p) => p.id === program.id)?.buildMs; + if (built?.baseline && built?.candidate) add([built.baseline, built.candidate]); + } + for (let run = 0; run < runs; run++) add(await pair(program)); + if (row.ratios.length === 0) continue; + rows.push(row); + console.log( + `time ${program.id} ${fmtMs(median(row.baselineMs))} -> ${fmtMs(median(row.candidateMs))} ` + + `(${pct(median(row.ratios))}, ${row.ratios.length} pairs)`, + ); + } + report.timing = { runs: timingRuns, maxGrowth: maxTimeGrowth, rows, groups: {} }; + const groups = { + bench: rows.filter((r) => r.id.startsWith("bench/")), + self: rows.filter((r) => r.id === "self/cli"), + }; + for (const [name, group] of Object.entries(groups)) { + if (group.length === 0) continue; + const result = timeRatio(group); + report.timing.groups[name] = result; + const verdict = + result.low > 1 + maxTimeGrowth + ? "SLOWER" + : result.high > 1 + maxTimeGrowth + ? "inconclusive (rerun with more runs)" + : "within tolerance"; + console.log( + `time ${name} ${group.length > 1 ? "geomean " : ""}${pct(result.ratio)} ` + + `[95% CI ${pct(result.low)}, ${pct(result.high)}]: ${verdict}`, + ); + if (result.low > 1 + maxTimeGrowth) { + failed = true; + report.failures.push( + `${name} compile time grew ${pct(result.ratio)} (CI low ${pct(result.low)})`, + ); + } + } +} + +const counts = {}; +for (const p of report.programs) counts[p.verdict] = (counts[p.verdict] ?? 0) + 1; +report.counts = counts; +report.verdict = failed ? "fail" : "pass"; +report.elapsedMs = Date.now() - started; +if (values.json) { + mkdirSync(dirname(resolve(values.json)), { recursive: true }); + writeFileSync(values.json, JSON.stringify(report, null, 2) + "\n"); +} +if (!values.keep) rmSync(work, { recursive: true, force: true }); +else console.log(`artifacts kept in ${work}`); +console.log( + `zero-cost gate: ${failed ? "FAIL" : "PASS"} — ${Object.entries(counts) + .map(([k, v]) => `${v} ${k}`) + .join(", ")} in ${fmtMs(report.elapsedMs)}`, +); +for (const failure of report.failures) console.log(` ${failure}`); +process.exit(failed ? 1 : 0); diff --git a/tests/corpus/5633-zlib-typed-options.ts b/tests/corpus/5633-zlib-typed-options.ts new file mode 100644 index 000000000..da657ca14 --- /dev/null +++ b/tests/corpus/5633-zlib-typed-options.ts @@ -0,0 +1,58 @@ +// zlib options held in a typed object (a cast object literal that crosses +// into the options slot by reference) are read like a plain options +// object: levels, windowBits and strategies apply, and out-of-range values +// throw Node's ERR_OUT_OF_RANGE from the sync, callback, promisified and +// stream forms. +import * as zlib from "node:zlib"; +import { promisify } from "node:util"; + +const data = Buffer.from("hello hello hello hello hello"); +const stored = { level: 0 } as zlib.ZlibOptions; +const fast = { level: 1 } as zlib.ZlibOptions; +const huffman = { strategy: zlib.constants.Z_HUFFMAN_ONLY } as zlib.ZlibOptions; +const smallWindow = { windowBits: 9, memLevel: 1 } as zlib.ZlibOptions; +const badLevel = { level: 99 } as zlib.ZlibOptions; +const badWindow = { windowBits: 3 } as zlib.ZlibOptions; + +function attempt(label: string, run: () => void): void { + try { + run(); + console.log(label, "ok"); + } catch (e) { + const err = e as NodeJS.ErrnoException; + console.log(label, err.name, err.code, err.message); + } +} + +console.log("sync stored", zlib.deflateSync(data, stored).toString("hex")); +console.log("sync fast", zlib.deflateSync(data, fast).toString("hex")); +console.log("sync huffman", zlib.deflateSync(data, huffman).toString("hex")); +console.log("raw small window", zlib.deflateRawSync(data, smallWindow).toString("hex")); +console.log("gzip stored", zlib.gzipSync(data, stored).subarray(10).toString("hex")); +attempt("sync bad level", () => zlib.deflateSync(data, badLevel)); +attempt("sync bad window", () => zlib.inflateSync(zlib.deflateSync(data), badWindow)); +attempt("callback bad level", () => zlib.gzip(data, badLevel, () => console.log("not called"))); +attempt("stream bad level", () => zlib.createDeflate(badLevel)); + +const deflateAsync = promisify(zlib.deflate); + +async function main(): Promise { + const viaCallback = await new Promise((resolve, reject) => + zlib.deflate(data, stored, (err, out) => (err ? reject(err) : resolve(out))), + ); + console.log("callback stored", viaCallback.toString("hex")); + console.log("promise stored", (await deflateAsync(data, stored)).toString("hex")); + try { + await deflateAsync(data, badLevel); + console.log("promise bad level fulfilled"); + } catch (e) { + console.log("promise bad level", (e as NodeJS.ErrnoException).code); + } + const stream = zlib.createDeflate(fast); + const parts: Buffer[] = []; + stream.on("data", (chunk: Buffer) => parts.push(chunk)); + stream.on("end", () => console.log("stream fast", Buffer.concat(parts).toString("hex"))); + stream.end(data); +} + +main().catch((e) => console.log("unexpected", String(e))); diff --git a/tests/corpus/5683-date-local-fields-tz.ts b/tests/corpus/5683-date-local-fields-tz.ts new file mode 100644 index 000000000..dc75011ed --- /dev/null +++ b/tests/corpus/5683-date-local-fields-tz.ts @@ -0,0 +1,95 @@ +// Local-time getters read repeatedly on one Date and alternately on two, +// on Dates parsed from strings, and across process.env.TZ writes and +// deletes: every read answers the fields of the zone in effect at that +// moment, as in Node. + +function fields(d: Date): string { + return [ + d.getFullYear(), + d.getMonth(), + d.getDate(), + d.getDay(), + d.getHours(), + d.getMinutes(), + d.getSeconds(), + d.getMilliseconds(), + d.getTimezoneOffset(), + ].join(","); +} + +const a = new Date(Date.UTC(2024, 2, 10, 6, 59, 59, 999)); +const b = new Date(Date.UTC(2024, 2, 10, 7, 0, 0, 0)); +const c = new Date(Date.UTC(2024, 10, 3, 5, 30, 0, 250)); + +function report(label: string): void { + console.log(label, fields(a), fields(a)); + console.log(label, fields(b), fields(c), fields(b)); + let mixed = 0; + for (let i = 0; i < 6; i++) mixed += a.getHours() * 100 + b.getHours() + c.getDate(); + console.log(label, "alternating", mixed); +} + +report("start"); +process.env.TZ = "America/New_York"; +report("new york"); +process.env.TZ = "Asia/Kolkata"; +report("kolkata"); +process.env.TZ = "Australia/Lord_Howe"; +report("lord howe"); + +// Dates parsed from strings with an explicit offset, read in local time. +const d = new Date("2024-06-01T23:45:00+05:30"); +console.log("offset string", fields(d), d.getTime()); +const e = new Date("2024-07-11T00:15:30.001Z"); +console.log("offset string", fields(e), e.getTime()); +console.log("first again", fields(d)); + +delete process.env.TZ; +report("deleted"); +process.env.TZ = "UTC"; +report("utc"); + +// Many instants read field by field. +process.env.TZ = "Europe/Berlin"; +let sum = 0; +for (let i = 0; i < 2000; i++) { + const t = new Date(1700000000000 + i * 3_599_999); + sum += t.getHours() + t.getDate() * 3 + t.getMonth() * 7 + t.getTimezoneOffset(); +} +console.log("berlin sum", sum); + +const invalid = new Date(Number.NaN); +console.log("invalid", fields(invalid), fields(invalid)); + +// Instants around day and second boundaries, before the epoch and at the +// ends of the Date range, in UTC and local fields; then 2000-2030 in a zone +// with DST. +function utcFields(d: Date): string { + return [ + d.getUTCFullYear(), + d.getUTCMonth(), + d.getUTCDate(), + d.getUTCDay(), + d.getUTCHours(), + d.getUTCMinutes(), + d.getUTCSeconds(), + d.getUTCMilliseconds(), + ].join(","); +} +const edges = [ + 0, -0, -0.5, 0.5, -1, 1, -999, -1000, -1001, 999.9, -86_400_000, -86_400_001, 86_399_999, + -62_198_755_200_000, -62_198_755_200_001, 8_640_000_000_000_000, -8_640_000_000_000_000, + 8_639_999_999_999_999, -8_639_999_999_999_999, 951_782_400_000, 4_107_542_399_999, +]; +process.env.TZ = "UTC"; +for (const t of edges) { + const x = new Date(t); + console.log(t, utcFields(x), fields(x)); +} +process.env.TZ = "America/Sao_Paulo"; +let walk = 0; +for (let t = 946_684_800_000; t < 1_893_456_000_000; t += 77_777_777) { + const x = new Date(t); + walk = (walk * 31 + x.getUTCDate() + x.getUTCHours() * 7 + x.getDate() + x.getMinutes() + x.getDay()) % 1_000_003; +} +console.log("walk", walk); diff --git a/tests/corpus/5768-crypto-hash-unknown.ts b/tests/corpus/5768-crypto-hash-unknown.ts new file mode 100644 index 000000000..7948dc758 --- /dev/null +++ b/tests/corpus/5768-crypto-hash-unknown.ts @@ -0,0 +1,68 @@ +// Hash and Hmac values held in 'unknown' and 'any' slots: update(), +// digest() and Hash's copy() dispatch through the value, update() answers +// the same value, a cast back to Hash or Hmac keeps its identity, and the +// errors after digest() are Node's. An Hmac has no copy(). +import { createHash, createHmac, type Hash, type Hmac } from "node:crypto"; + +function code(run: () => unknown): string { + try { + const value = run(); + return typeof value === "string" ? `ok ${value.slice(0, 16)}` : `ok ${typeof value}`; + } catch (error) { + const e = error as Error & { code?: string }; + return `${e.name} ${e.code ?? ""} ${e.message}`; + } +} + +function hexOf(value: any): string { + return value.update("abc").digest("hex"); +} + +console.log("any param", hexOf(createHash("sha256"))); +console.log("any param hmac", hexOf(createHmac("sha256", "key"))); + +const hash = createHash("sha1"); +const held: unknown = hash; +console.log("typeof", typeof held, String(held), Object.prototype.toString.call(held)); +console.log("cast keeps identity", (held as Hash) === hash); +const chained: any = held; +console.log("update answers the value", chained.update("a") === chained); +console.log("typed digest after untyped update", hash.digest("hex")); +console.log("digest again", code(() => chained.digest("hex"))); +console.log("update after digest", code(() => chained.update("x"))); + +const values: unknown[] = [createHash("md5"), createHmac("sha512", "k"), createHash("sha256")]; +for (const value of values) { + const v = value as any; + v.update("one"); + v.update(Buffer.from("two")); + v.update("74687265", "hex"); + console.log("array", v.digest("base64")); +} + +const record: { label: string; hash: unknown } = { label: "rec", hash: createHash("sha384") }; +const fromRecord = record.hash as Hash; +fromRecord.update("record"); +console.log(record.label, fromRecord.digest("hex").slice(0, 32)); + +const original: any = createHash("sha256").update("prefix"); +const copy = original.copy(); +copy.update("-copy"); +original.update("-original"); +console.log("copy", copy.digest("hex").slice(0, 16), original.digest("hex").slice(0, 16)); +console.log("copy is a new value", copy === original); +const shake: any = createHash("shake256"); +shake.update("x"); +console.log("copy outputLength", shake.copy({ outputLength: 8 }).digest("hex")); +console.log("copy bad outputLength", code(() => shake.copy({ outputLength: -1 }))); + +const mac: unknown = createHmac("sha256", "secret"); +const macAny: any = mac; +console.log("hmac copy", typeof macAny.copy, macAny.copy === undefined); +console.log("hmac copy call", code(() => macAny.copy())); +console.log("hmac digest", (mac as Hmac).update("data").digest("hex").slice(0, 16)); +console.log("hmac digest again", JSON.stringify(macAny.digest("hex"))); + +console.log("missing member", macAny.nope, typeof chained.missing); +console.log("bad data", code(() => (createHash("sha256") as any).update(42))); +console.log("default digest", (createHash("md5") as any).update("z").digest().length); diff --git a/tests/corpus/5769-crypto-hash-unknown.cjs b/tests/corpus/5769-crypto-hash-unknown.cjs new file mode 100644 index 000000000..90782757c --- /dev/null +++ b/tests/corpus/5769-crypto-hash-unknown.cjs @@ -0,0 +1,77 @@ +// Hash and Hmac values passed through untyped JavaScript code: function +// parameters, object fields, arrays and Map values. update() answers the +// same value, digest() and copy() follow Node, and Hmac has no copy(). +const crypto = require("node:crypto"); + +function code(run) { + try { + const value = run(); + return typeof value === "string" ? `ok ${value.slice(0, 16)}` : `ok ${typeof value}`; + } catch (error) { + return `${error.name} ${error.code || ""} ${error.message}`; + } +} + +function digestAll(items, encoding) { + return items.map((item) => item.update("payload").digest(encoding)); +} + +console.log(digestAll([crypto.createHash("sha256"), crypto.createHmac("sha1", "k")], "hex")); + +const box = { hash: crypto.createHash("md5"), mac: crypto.createHmac("sha256", Buffer.from("key")) }; +box.hash.update("a").update("b"); +console.log("field", box.hash.digest("hex"), box.mac.update("c").digest("base64url")); + +const byName = new Map(); +byName.set("sha512", crypto.createHash("sha512")); +byName.set("hmac", crypto.createHmac("md5", "x")); +for (const [name, value] of byName) { + const same = value.update(name); + console.log("map", name, same === value, value.digest("hex").slice(0, 20)); +} + +function copyTwice(h) { + const a = h.copy(); + const b = h.copy(); + a.update("1"); + b.update("2"); + return [a.digest("hex").slice(0, 12), b.digest("hex").slice(0, 12), h.digest("hex").slice(0, 12)]; +} +console.log("copies", copyTwice(crypto.createHash("sha256").update("base"))); + +function describe(value) { + return [typeof value, String(value), Object.prototype.toString.call(value), value.notAMember]; +} +console.log("hash", describe(crypto.createHash("sha256"))); +console.log("hmac", describe(crypto.createHmac("sha256", "k"))); +const plainMac = [crypto.createHmac("sha256", "k")][0]; +console.log("hmac copy read", typeof plainMac.copy, plainMac.copy === undefined); + +const mac = crypto.createHmac("sha256", "k"); +const viaObject = { mac }; +console.log("hmac copy", code(() => viaObject.mac.copy())); +console.log("digest", viaObject.mac.digest("hex").slice(0, 16), JSON.stringify(viaObject.mac.digest("hex"))); +console.log("update after digest", code(() => viaObject.mac.update("x"))); + +const used = [crypto.createHash("sha1")]; +used[0].digest(); +console.log("digest twice", code(() => used[0].digest("hex"))); +console.log("copy after digest", code(() => used[0].copy())); +console.log("update bad", code(() => [crypto.createHash("sha1")][0].update(null))); + +function methods(h, m) { + return [h.update === m.update, h.digest === m.digest, typeof h.copy, typeof m.copy, + h.update.name, h.update.length, h.digest.name, h.digest.length, h.copy.length]; +} +console.log("methods", methods(crypto.createHash("sha256"), crypto.createHmac("sha1", "k"))); +function viaCall(h) { + const update = h.update; + const digest = h.digest; + return [update.call(h, "abc") === h, digest.call(h, "hex").slice(0, 16)]; +} +console.log("call", viaCall(crypto.createHash("sha256")), viaCall(crypto.createHmac("sha256", "k"))); +function sharedUpdate(h, m) { + h.update.call(m, "shared"); + return m.digest("hex").slice(0, 16); +} +console.log("shared update", sharedUpdate(crypto.createHash("md5"), crypto.createHmac("md5", "k"))); diff --git a/tests/corpus/5774-crypto-handle-long-callee.cjs b/tests/corpus/5774-crypto-handle-long-callee.cjs new file mode 100644 index 000000000..89a5fbbbf --- /dev/null +++ b/tests/corpus/5774-crypto-handle-long-callee.cjs @@ -0,0 +1,46 @@ +// A missing method called on a Hash, Hmac or KeyObject held in an untyped +// value throws Node's TypeError with the whole callee text, also when the +// text is longer than a fixed-size message buffer. +const { createHash, createHmac, createSecretKey } = require("node:crypto"); + +const holder = { + aHashValueStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixtyBytes: + createHash("sha256"), + anHmacValueStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixtyBytes: + createHmac("sha256", "key"), + aSecretKeyObjectStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixty: + createSecretKey(Buffer.alloc(16, 1)), +}; + +function report(label, run) { + try { + run(); + console.log(label, "did not throw"); + } catch (e) { + console.log(label, e instanceof TypeError, e.name, e.message.length); + console.log(e.message); + } +} + +report("hash", () => + holder.aHashValueStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixtyBytes.missingMethodOnTheHashValue(), +); +report("hmac", () => + holder.anHmacValueStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixtyBytes.missingMethodOnTheHmacValue(), +); +report("key", () => + holder.aSecretKeyObjectStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixty.missingMethodOnTheKeyObject(), +); + +// A short callee text through an untyped parameter, for comparison. +function callMissing(value) { + return value.nope(); +} +report("short", () => + callMissing(holder.aHashValueStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixtyBytes), +); +console.log( + holder.aHashValueStoredUnderAPropertyNameLongEnoughThatTheCalleeTextOfTheMissingMethodRunsPastOneHundredAndSixtyBytes + .update("abc") + .digest("hex"), +); diff --git a/tests/corpus/5908-iterator-helper-objects.ts b/tests/corpus/5908-iterator-helper-objects.ts new file mode 100644 index 000000000..5e57b9ddf --- /dev/null +++ b/tests/corpus/5908-iterator-helper-objects.ts @@ -0,0 +1,214 @@ +// Iterator helpers over generators, stored array iterators and Map/Set +// iterators: chains that end in a consuming method, helper objects driven +// by next(), for...of, spreads, Array.from and yield*, and the points at +// which a helper closes its source (runs its return()). +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; +} + +// Consuming methods over generator chains. +flush(count("a", 5).map((x) => x * 10).filter((x) => x !== 20).toArray()); +flush(count("b", 4).reduce((acc, x) => acc + x)); +flush(count("c", 4).reduce((acc, x, i) => acc + x * i, 100)); +flush(count("d", 5).some((x) => x === 2)); +flush(count("e", 5).every((x) => x < 3)); +flush(count("f", 5).find((x) => x > 3) ?? -1); +flush(count("g", 2).find((x) => x > 3) ?? -1); +let each = ""; +count("h", 3).forEach((x, i) => { + each += `${x}@${i} `; +}); +flush(each.trim()); + +// take, drop and their budgets. +flush(count("i", 9).drop(2).take(3).toArray()); +flush(count("j", 3).take(Infinity).toArray()); +flush([count("k", 4).take(2.9).toArray(), count("l", 4).drop(1.5).toArray()]); +for (const budget of [-1, NaN, -0.5]) { + try { + console.log(count("m", 1).take(budget).toArray()); + } catch (e) { + console.log((e as Error).name, (e as Error).message); + } +} +try { + count("n", 0).reduce((a, b) => a + b); +} catch (e) { + console.log((e as Error).name, (e as Error).message); +} +flush("budgets"); + +// An invalid budget closes a started source before the RangeError. +const invalid = count("bad", 3); +invalid.next(); +try { + invalid.take(-1); +} catch (e) { + log.push((e as Error).message); +} +flush(invalid.next().done); + +// A started source closes through take(0) without another pull. +const started = count("o", 3); +started.next(); +flush([started.take(0).toArray(), started.next().done]); + +// Helper objects step lazily, one source value per delivered value. +const h = count("p", 4) + .map((x) => x * 2) + .filter((x) => x !== 4); +log.push("made"); +console.log(h.next(), h.next()); +console.log(h.next(), h.next()); +flush("next"); +const words = count("q", 3).map((x, i) => "w".repeat(x) + i); +flush([...words]); +flush(Array.from(count("r", 3).drop(1))); +function* wrap(): Generator { + yield 0; + yield* count("s", 2).map((x) => -x); + yield 9; +} +flush([...wrap()]); +function sum(it: Iterator): number { + let total = 0; + for (let r = it.next(); !r.done; r = it.next()) total += r.value; + return total; +} +flush(sum(count("t", 4).filter((x) => x % 2 === 0))); + +// next() from inside the helper's own callback. +const self: IteratorObject = count("re", 2).map((x) => { + if (x === 2) { + try { + self.next(); + } catch (e) { + log.push(`${(e as Error).name}: ${(e as Error).message}`); + } + } + return x; +}); +flush(self.toArray()); + +// Leaving a for...of over a helper closes the helper and its source. +for (const x of count("u", 9).map((v) => v * v)) { + if (x > 4) break; + log.push(`got${x}`); +} +flush("break"); +try { + count("v", 3).forEach((x) => { + if (x === 2) throw new Error("callback threw"); + }); +} catch (e) { + log.push((e as Error).message); +} +flush("throw"); +function* failing(): Generator { + try { + yield 1; + throw new Error("source threw"); + } finally { + log.push("failing:finally"); + } +} +try { + failing().map((x) => x).toArray(); +} catch (e) { + log.push((e as Error).message); +} +flush("source"); +// When closing the source throws while another error propagates, the +// first error is the one that reaches the caller. +function* closeThrows(): Generator { + try { + yield 1; + } finally { + log.push("closeThrows:finally"); + throw new Error("close threw"); + } +} +try { + closeThrows() + .map((x): number => { + if (x > 0) throw new Error("callback threw"); + return x; + }) + .toArray(); +} catch (e) { + log.push((e as Error).message); +} +try { + for (const x of closeThrows().filter(() => true)) throw new Error(`body threw at ${x}`); +} catch (e) { + log.push((e as Error).message); +} +try { + for (const x of closeThrows()) throw new Error(`plain loop threw at ${x}`); +} catch (e) { + log.push((e as Error).message); +} +flush("close errors"); + +// flatMap over arrays and generators; take stops mid-way through an inner +// generator and closes it before the outer source. +flush(count("w", 2).flatMap((x) => [x, -x]).toArray()); +flush(count("x", 3).flatMap((x) => count(`y${x}-`, 2)).take(3).toArray()); + +// Map and Set iterators, observed live. +const set = new Set([1, 2, 3]); +console.log( + set + .values() + .map((x) => { + if (x === 1) set.add(4); + if (x === 2) set.delete(3); + return x * 100; + }) + .toArray(), +); +const map = new Map([ + ["a", 1], + ["b", 2], + ["c", 3], +]); +const pairs = map.entries().filter(([, v]) => v !== 2); +map.set("d", 4); +console.log([...pairs].map(([k, v]) => k + v).join(",")); +console.log(map.keys().map((k) => k.toUpperCase()).toArray(), map.values().reduce((a, b) => a + b, 0)); +console.log(set.keys().some((x) => x > 3), set.entries().find(([k]) => k === 2)?.[1]); + +// Array iterators as helper sources. +const nums = [5, 6, 7]; +const doubled = nums.values().map((x) => x * 2); +nums.push(8); +console.log([...doubled], nums.keys().filter((i) => i > 0).toArray(), [...nums.entries().drop(2)]); + +// Records and strings flow through the stages by reference. +interface Item { + name: string; + qty: number; +} +const items: Item[] = [ + { name: "pen", qty: 3 }, + { name: "ink", qty: 0 }, + { name: "pad", qty: 7 }, +]; +function* stock(): Generator { + yield* items; +} +const inStock = stock().filter((it) => it.qty > 0); +console.log(inStock.next().value === items[0], stock().find((it) => it.name === "pad")?.qty); +console.log(stock().map((it) => `${it.name}x${it.qty}`).toArray().join(" ")); diff --git a/tests/corpus/5909-iterator-helper-objects-js.js b/tests/corpus/5909-iterator-helper-objects-js.js new file mode 100644 index 000000000..759cc2321 --- /dev/null +++ b/tests/corpus/5909-iterator-helper-objects-js.js @@ -0,0 +1,58 @@ +// Iterator helper objects in JavaScript: inferred callback parameters, +// return() on a helper (before its first next(), mid-way and after +// completion), and chains over Map/Set iterators. +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; +} + +flush(count("a", 4).map((x) => x + 0.5).toArray()); +flush(count("b", 6).filter((x) => x % 2 === 1).map((x, i) => x * 10 + i).take(2).toArray()); +flush(count("c", 3).reduce((acc, x) => acc + x, "")); + +// return() before the first next() closes a source that has started. +const started = count("d", 3); +started.next(); +const mapped = started.map((x) => x * 2); +flush([mapped.return(), started.next()]); +// ... and leaves an unstarted source without running its body. +const fresh = count("e", 3); +const filtered = fresh.filter(() => true); +flush([filtered.return(), fresh.next(), filtered.next()]); +// return() at a yield closes the source; later calls report done. +const stepping = count("f", 5).take(4); +flush([stepping.next(), stepping.return(), stepping.next(), stepping.return()]); +// A chain of helpers closes through to the source. +const chained = count("g", 5); +chained.next(); +const chain = chained.map((x) => x).filter(() => true).take(3); +flush([chain.return(), chained.next()]); + +// Map and Set iterators. +const scores = new Map([ + ["ann", 3], + ["bob", 9], + ["cy", 5], +]); +console.log(scores.entries().filter(([, v]) => v > 4).map(([k]) => k).toArray()); +console.log(scores.values().every((v) => v > 2), scores.keys().find((k) => k.length === 2)); +const tags = new Set(["x", "y", "z"]); +const upper = tags.values().map((t) => t.toUpperCase()); +tags.add("w"); +console.log([...upper], tags.keys().drop(3).toArray()); +for (const t of tags.values().flatMap((t) => [t, t + t])) { + if (t === "yy") break; + log.push(t); +} +flush("flat"); diff --git a/tests/corpus/5919-record-unions-shared-arms.ts b/tests/corpus/5919-record-unions-shared-arms.ts new file mode 100644 index 000000000..89f34dd83 --- /dev/null +++ b/tests/corpus/5919-record-unions-shared-arms.ts @@ -0,0 +1,60 @@ +// Record unions that share arms: Third = D | A, First = A | B and +// Second = B | C. Only C is cyclic. Every arm of a union whose values the +// cycle collector can visit carries a collector header, and the shared +// arms pass that requirement from Second back through First to Third. +type A = { kind: "a"; v: number }; +type B = { kind: "b"; w: number }; +type C = { kind: "c"; kids: C[] }; +type D = { kind: "d"; label: string }; +type Third = D | A; +type First = A | B; +type Second = B | C; + +function third(u: Third): string { + return u.kind === "d" ? u.label : `a${u.v}`; +} + +function first(u: First): number { + return u.kind === "a" ? u.v : u.w; +} + +function second(u: Second): number { + return u.kind === "b" ? u.w : u.kids.length + u.kids.reduce((n, k) => n + second(k), 0); +} + +const zs: Third[] = [ + { kind: "d", label: "dee" }, + { kind: "a", v: 7 }, +]; +console.log(zs.map(third).join(",")); + +const xs: First[] = [ + { kind: "a", v: 1 }, + { kind: "b", w: 2 }, +]; +console.log(xs.map(first).join(",")); + +const leaf: C = { kind: "c", kids: [] }; +const ys: Second[] = [ + { kind: "b", w: 3 }, + { kind: "c", kids: [leaf, { kind: "c", kids: [leaf] }] }, +]; +console.log(ys.map(second).join(",")); + +// One record held through two unions. +const sharedA: A = { kind: "a", v: 5 }; +const sharedB: B = { kind: "b", w: 6 }; +const viaThird: Third = sharedA; +const viaFirst: First[] = [sharedA, sharedB]; +const viaSecond: Second = sharedB; +console.log(third(viaThird), viaFirst.map(first).join(","), second(viaSecond)); + +// A cyclic C reachable only through Second, dropped and rebuilt in a loop. +let total = 0; +for (let i = 0; i < 200; i++) { + const node: C = { kind: "c", kids: [] }; + node.kids.push(node); + const held: Second[] = [node, { kind: "b", w: i }]; + total += held.length + node.kids.length; +} +console.log("total", total); diff --git a/tests/corpus/5930-record-union-pointers.ts b/tests/corpus/5930-record-union-pointers.ts new file mode 100644 index 000000000..01882d730 --- /dev/null +++ b/tests/corpus/5930-record-union-pointers.ts @@ -0,0 +1,79 @@ +// Unions whose arms are all object literals (discriminated unions) hold the +// record itself: construction, narrowing, storage in arrays, maps, fields +// and closures, identity, and printing behave exactly as in Node. +type Shape = + | { kind: "circle"; r: number } + | { kind: "rect"; w: number; h: number } + | { kind: "tri"; b: number; h: number; label: string }; + +function area(s: Shape): number { + switch (s.kind) { + case "circle": + return Math.round(3.14159 * s.r * s.r); + case "rect": + return s.w * s.h; + case "tri": + return 0.5 * s.b * s.h; + } +} + +function make(i: number): Shape { + if (i % 3 === 0) return { kind: "circle", r: i }; + if (i % 3 === 1) return { kind: "rect", w: i, h: i + 1 }; + return { kind: "tri", b: i, h: 2, label: "t" + i }; +} + +const shapes: Shape[] = []; +for (let i = 0; i < 9; i++) shapes.push(make(i)); +let total = 0; +for (const s of shapes) total += area(s); +console.log("total", total); +console.log(shapes.map((s) => s.kind).join(",")); +console.log(shapes.filter((s) => s.kind === "rect").length); + +// Identity: the same record compares equal, a structurally equal one does not. +const first = shapes[0]!; +const again: Shape = shapes[0]!; +const copy: Shape = { kind: "circle", r: 0 }; +console.log(first === again, first === copy, shapes.indexOf(again), shapes.includes(copy)); + +// Mutation through one alias is visible through the union value. +const rect = { kind: "rect" as const, w: 2, h: 3 }; +const asShape: Shape = rect; +rect.w = 10; +console.log(area(asShape), asShape === rect); + +// Fields, maps and closures holding union values. +interface Holder { + current: Shape; + history: Shape[]; +} +const holder: Holder = { current: make(1), history: [] }; +for (let i = 0; i < 4; i++) { + holder.history.push(holder.current); + holder.current = make(i + 5); +} +console.log(holder.current.kind, holder.history.map(area).join(" ")); + +const byName = new Map(); +for (const s of shapes) byName.set(s.kind + area(s), s); +console.log(byName.size, byName.get("rect2")?.kind, byName.get("missing") === undefined); + +const pick = (k: Shape["kind"]): (() => Shape | undefined) => { + const found = shapes.find((s) => s.kind === k); + return () => found; +}; +const getTri = pick("tri"); +const tri = getTri(); +console.log(tri !== undefined && tri.kind === "tri" ? tri.label : "none"); + +// Printing and serialization see the plain objects. +console.log(shapes.slice(0, 3)); +console.log(JSON.stringify(shapes.slice(0, 3))); +const second = shapes[2]!; +console.log(`${String(shapes[1]!.kind)} ${"label" in second} ${"label" in shapes[1]!}`); + +// Spread copies and sorting. +const grown = shapes.map((s): Shape => (s.kind === "circle" ? { ...s, r: s.r + 1 } : s)); +grown.sort((a, b) => area(a) - area(b)); +console.log(grown.map((s) => `${s.kind}:${area(s)}`).join(" ")); diff --git a/tests/corpus/5931-record-union-unit-arms.ts b/tests/corpus/5931-record-union-unit-arms.ts new file mode 100644 index 000000000..1ade8a0a3 --- /dev/null +++ b/tests/corpus/5931-record-union-unit-arms.ts @@ -0,0 +1,67 @@ +// Record unions with undefined and null arms: NULL and the null sentinel +// encode the unit arms, optional properties keep their absent state, and +// narrowing, nullish operators and equality agree with Node. +type Ok = { ok: true; value: number }; +type Err = { ok: false; error: string }; +type Res = Ok | Err; + +function parse(text: string): Res { + const n = Number(text); + return Number.isNaN(n) ? { ok: false, error: `bad ${text}` } : { ok: true, value: n }; +} + +function lookup(table: Res[], i: number): Res | undefined { + return i < table.length ? table[i] : undefined; +} + +function maybe(i: number): Res | null | undefined { + if (i % 3 === 0) return null; + if (i % 3 === 1) return undefined; + return parse(String(i)); +} + +const table = ["1", "x", "3"].map(parse); +for (let i = 0; i < 4; i++) { + const r = lookup(table, i); + if (r === undefined) console.log(i, "missing"); + else if (r.ok) console.log(i, "value", r.value); + else console.log(i, "error", r.error); +} + +for (let i = 0; i < 6; i++) { + const m = maybe(i); + const tag = m === null ? "null" : m === undefined ? "undefined" : m.ok ? "ok" : "err"; + console.log(i, tag, m == null, m ?? "default", typeof m); +} + +// Optional properties of a record-union type. +interface Slot { + name: string; + result?: Res; + last?: Res | null; +} +const slots: Slot[] = [ + { name: "a" }, + { name: "b", result: parse("7") }, + { name: "c", result: parse("q"), last: null }, +]; +for (const s of slots) { + console.log(s.name, "result" in s, s.result?.ok, s.last === null, s.last === undefined); +} +console.log(JSON.stringify(slots)); +slots[0]!.result = table[1]; +slots[1]!.result = undefined; +console.log(slots.map((s) => (s.result === undefined ? "-" : s.result.ok ? "ok" : "err")).join("")); + +// Equality across arms and unit encodings. +const a: Res | undefined = table[0]; +const b: Res | undefined = undefined; +const c: Res | null = null; +console.log(a === table[0], a === b, b === undefined, c === null, (c as Res | null) === table[2]); + +// Unions of records reaching unknown and back. +const values: unknown[] = [table[0], table[1], undefined, null]; +for (const v of values) { + if (v === null || v === undefined) console.log("unit", v); + else console.log("object", JSON.stringify(v)); +} diff --git a/tests/corpus/5932-record-union-trees.ts b/tests/corpus/5932-record-union-trees.ts new file mode 100644 index 000000000..060cadc4b --- /dev/null +++ b/tests/corpus/5932-record-union-trees.ts @@ -0,0 +1,77 @@ +// A recursive discriminated union (an expression tree) whose leaf arm holds +// no references: every arm shares the cycle collector's header, so trees +// can be built, rewritten, shared and dropped in cycles. +type Expr = + | { kind: "num"; value: number } + | { kind: "neg"; arg: Expr } + | { kind: "add"; left: Expr; right: Expr } + | { kind: "ref"; target: { expr: Expr | null } }; + +function evaluate(e: Expr): number { + switch (e.kind) { + case "num": + return e.value; + case "neg": + return -evaluate(e.arg); + case "add": + return evaluate(e.left) + evaluate(e.right); + case "ref": + return e.target.expr === null ? 0 : evaluate(e.target.expr); + } +} + +function build(depth: number, seed: number): Expr { + if (depth === 0) return { kind: "num", value: seed % 10 }; + if (seed % 4 === 0) return { kind: "neg", arg: build(depth - 1, seed + 1) }; + return { kind: "add", left: build(depth - 1, seed * 3 + 1), right: build(depth - 1, seed + 7) }; +} + +function simplify(e: Expr): Expr { + switch (e.kind) { + case "neg": { + const inner = simplify(e.arg); + return inner.kind === "neg" ? inner.arg : inner.kind === "num" ? { kind: "num", value: -inner.value } : { kind: "neg", arg: inner }; + } + case "add": { + const l = simplify(e.left); + const r = simplify(e.right); + if (l.kind === "num" && r.kind === "num") return { kind: "num", value: l.value + r.value }; + return l === e.left && r === e.right ? e : { kind: "add", left: l, right: r }; + } + default: + return e; + } +} + +function show(e: Expr): string { + switch (e.kind) { + case "num": + return String(e.value); + case "neg": + return `-(${show(e.arg)})`; + case "add": + return `(${show(e.left)} + ${show(e.right)})`; + case "ref": + return "ref"; + } +} + +let checksum = 0; +for (let round = 0; round < 50; round++) { + const tree = build(6, round); + const simple = simplify(tree); + checksum += evaluate(tree) * 3 + evaluate(simple); + // A cycle through a record arm: the tree refers back to itself. + const target: { expr: Expr | null } = { expr: null }; + const loop: Expr = { kind: "add", left: tree, right: { kind: "ref", target } }; + target.expr = { kind: "num", value: round }; + checksum += evaluate(loop); + target.expr = loop.left; +} +console.log(checksum); +console.log(show(simplify(build(3, 5)))); +console.log(show(build(2, 4)), evaluate(build(4, 2))); + +const shared: Expr = { kind: "num", value: 4 }; +const twice: Expr = { kind: "add", left: shared, right: shared }; +console.log(evaluate(twice), twice.kind === "add" && twice.left === twice.right); diff --git a/tests/corpus/5933-record-union-conversions.ts b/tests/corpus/5933-record-union-conversions.ts new file mode 100644 index 000000000..80b411a9b --- /dev/null +++ b/tests/corpus/5933-record-union-conversions.ts @@ -0,0 +1,78 @@ +// Record unions meet other representations: widening into unions with +// string and number arms, narrowing back, `in` tests on undiscriminated +// arms, generic helpers, class fields and promises. +type Point = { x: number; y: number }; +type Named = { name: string }; +type Item = Point | Named; +type Wide = Item | string | number; + +function describe(i: Item): string { + return "x" in i ? `point(${i.x},${i.y})` : `named(${i.name})`; +} + +function widen(i: Item): Wide { + return i; +} + +function narrow(w: Wide): Item | undefined { + if (typeof w === "string" || typeof w === "number") return undefined; + return w; +} + +const items: Item[] = [{ x: 1, y: 2 }, { name: "a" }, { x: 3, y: 4 }]; +const wide: Wide[] = items.map(widen); +wide.push("text", 42); +for (const w of wide) { + const back = narrow(w); + console.log(typeof w, back === undefined ? "-" : describe(back), back !== undefined && items.includes(back)); +} + +// Generic helpers instantiate over the union. +function firstWhere(xs: T[], pred: (x: T) => boolean): T | undefined { + for (const x of xs) if (pred(x)) return x; + return undefined; +} +const named = firstWhere(items, (i) => "name" in i); +console.log(named === items[1], named !== undefined ? describe(named) : "none"); +const pair: [Item, Item] = [items[2]!, items[0]!]; +const [p0, p1] = pair; +console.log(describe(p0), describe(p1)); + +// Class fields and methods. +class Cursor { + private at: Item; + readonly seen: Item[] = []; + constructor(start: Item) { + this.at = start; + } + move(next: Item): Item { + const prev = this.at; + this.seen.push(prev); + this.at = next; + return prev; + } + get current(): Item { + return this.at; + } +} +const cursor = new Cursor(items[0]!); +for (const i of items) cursor.move(i); +console.log(describe(cursor.current), cursor.seen.map(describe).join(" ")); + +// Promises carry union payloads. +async function load(i: number): Promise { + await null; + return items[i % items.length]!; +} +async function main(): Promise { + const loaded: Item[] = []; + for (const i of [0, 1, 5]) loaded.push(await load(i)); + console.log(loaded.map(describe).join(" "), loaded[0] === items[0]); + const slow = new Promise((resolve) => setTimeout(() => resolve({ x: 9, y: 9 }), 5)); + const raced = await Promise.race([load(1), slow]); + console.log(describe(raced)); + const pending: Promise[] = [load(2), load(0)]; + const all = await Promise.all(pending); + console.log(all.map(describe).join(" "), all[1] === items[0]); +} +main().then(() => console.log("done")); diff --git a/tests/corpus/5934-record-union-set-members.ts b/tests/corpus/5934-record-union-set-members.ts new file mode 100644 index 000000000..46f838870 --- /dev/null +++ b/tests/corpus/5934-record-union-set-members.ts @@ -0,0 +1,25 @@ +// A record union used as Set elements and Map keys keeps the runtime's +// tagged representation; other unions over the same records convert to and +// from it without changing identity semantics. +type Point = { x: number; y: number }; +type Named = { name: string }; +type Item = Point | Named; + +const items: Item[] = [{ x: 1, y: 2 }, { name: "a" }, { x: 3, y: 4 }]; +const marked = new Set(); +marked.add(items[0]!); +marked.add(items[0]!); +marked.add({ x: 1, y: 2 }); +console.log(marked.size, marked.has(items[0]!), marked.has(items[1]!)); + +const counts = new Map(); +for (const i of [...items, items[0]!, items[2]!]) counts.set(i, (counts.get(i) ?? 0) + 1); +console.log([...counts.values()].join(",")); + +function maybeItem(i: number): Item | undefined { + return i < items.length ? items[i] : undefined; +} +for (let i = 0; i < 4; i++) { + const m = maybeItem(i); + console.log(i, m === undefined ? "none" : "x" in m ? m.x : m.name, m !== undefined && marked.has(m)); +} diff --git a/tests/corpus/5943-fields-read-before-assignment.ts b/tests/corpus/5943-fields-read-before-assignment.ts deleted file mode 100644 index 922b3c34d..000000000 --- a/tests/corpus/5943-fields-read-before-assignment.ts +++ /dev/null @@ -1,181 +0,0 @@ -// @transform-types -// Fields that a base constructor (or the constructor itself) can read -// through an override or a method call before they are assigned hold -// undefined then, as in Node, for every field kind. After construction -// the fields read and update normally. -class Point { - x: number; - constructor(x: number) { - this.x = x; - } -} -function show(label: string, value: number): string { - return `${label}=${value}`; -} -abstract class Widget { - log: string[] = []; - constructor() { - this.describe("base"); - } - abstract describe(when: string): void; -} -class Panel extends Widget { - label: string; - width: number; - visible: boolean; - origin: Point; - items: number[]; - options: { depth: number }; - key: string | number; - index: Map; - tags: Set; - count = 5; - constructor(label: string, readonly id: string) { - super(); - this.label = label; - this.width = 10; - this.visible = true; - this.origin = new Point(1); - this.items = [1, 2]; - this.options = { depth: 2 }; - this.key = 7; - this.index = new Map([["a", 1]]); - this.tags = new Set(["t"]); - this.describe("ctor"); - } - describe(when: string): void { - console.log(when, this.label, this.width, this.visible, this.origin, this.items, this.options, this.key, this.count, this.id); - console.log(when, `${this.label}:${this.width}`, this.width + 1, this.count * 2, this.label === undefined, typeof this.items, this.origin?.x, this.options?.depth ?? -1, this.index?.size, this.tags?.has("t")); - console.log(when, show(this.label, this.width), String(this.key), [this.label].length); - try { - console.log(this.label.length, this.items.length); - } catch (error) { - console.log(when, (error as Error).message); - } - } - grow(): number { - this.count++; - this.width += 5; - this.label += "!"; - this.items.push(this.width); - return this.count + this.width + this.items.length; - } -} -const panel = new Panel("main", "p1"); -console.log(panel.grow(), panel.label, panel.items, panel.count, panel.width); - -class Plain { - total: number; - name: string; - constructor() { - this.total = 1; - this.name = "plain"; - this.bump(); - } - bump(): void { - this.total++; - } -} -const plain = new Plain(); -console.log(plain.total, plain.name); - -class Toucher { - constructor() { - this.touch(); - } - touch(): void {} -} -class Counter extends Toucher { - count = 0; - label: string; - items: string[]; - constructor() { - super(); - this.label = "c"; - this.items = []; - } - override touch(): void { - this.count++; - this.label += "x"; - console.log(this.count, this.label, Number.isNaN(this.count)); - try { - this.items.push("a"); - } catch (error) { - console.log(error instanceof TypeError); - } - } -} -const c = new Counter(); -c.touch(); -console.log(c.count, c.label, c.items); - -class Shower { - constructor() { - this.show(); - } - show(): void {} -} -class Box extends Shower { - label: string; - value: T; - constructor(value: T) { - super(); - this.label = "box"; - this.value = value; - } - override show(): void { - console.log("show", this.label, this.value); - } -} -new Box(3).show(); -new Box("s").show(); - -type Ctor = new (...args: any[]) => T; -class Viewer { - constructor() { - this.show(); - } - show(): void {} -} -function Labeled>(Parent: B) { - return class extends Parent { - label = "mixed"; - override show(): void { - console.log("show", this.label); - } - }; -} -const L = Labeled(Viewer); -new L().show(); - -interface Crash { loc: number; message: string } -class Setupper { - constructor() { - this.setup(); - } - setup(): void {} -} -class Host extends Setupper { - crash: Crash | null; - name: string | null; - constructor(c: Crash | null) { - super(); - this.crash = c; - this.name = "host"; - } - override setup(): void { - console.log("setup", this.crash, this.name); - if (this.crash !== null) { - try { - console.log(this.crash.loc); - } catch (error) { - console.log(error instanceof TypeError); - } - } - } - report(): string { - if (this.crash) return `${this.crash.message} at ${this.crash.loc}`; - return this.name === null ? "none" : this.name.toUpperCase(); - } -} -console.log(new Host({ loc: 3, message: "boom" }).report(), new Host(null).report()); diff --git a/tests/corpus/5944-js-fields-read-before-assignment.js b/tests/corpus/5944-js-fields-read-before-assignment.js deleted file mode 100644 index e313a12b0..000000000 --- a/tests/corpus/5944-js-fields-read-before-assignment.js +++ /dev/null @@ -1,28 +0,0 @@ -// JavaScript class fields that the constructor creates after it calls a -// method, or after a base constructor that does, hold undefined until -// they are assigned, as in Node. -class Widget { - constructor(name) { - this.render(); - this.name = name; - this.size = 3; - this.title = "widget"; - this.parts = ["a"]; - this.meta = { id: 1 }; - } - render() { - console.log("render", this.name, this.title, this.size, this.parts, this.meta, this.size + 1, `${this.name}!`); - } -} -class Big extends Widget { - constructor() { - super("big"); - this.extra = 2; - } - render() { - super.render(); - console.log("big", this.extra); - } -} -new Widget("w").render(); -new Big().render(); diff --git a/tests/corpus/5973-buffer-concat-untyped.js b/tests/corpus/5973-buffer-concat-untyped.js new file mode 100644 index 000000000..116ca0a2b --- /dev/null +++ b/tests/corpus/5973-buffer-concat-untyped.js @@ -0,0 +1,38 @@ +// Buffer.concat over an untyped list (JavaScript's `const chunks = []`): +// Uint8Array elements concatenate, the length form pads or truncates, and +// a non-array list or a non-Uint8Array element throws Node's TypeError. The +// length form checks the length before the elements, and an empty list +// returns before either. +const chunks = []; +chunks.push(Buffer.from("ab")); +chunks.push(new Uint8Array([99, 100])); +console.log(Buffer.concat(chunks).toString()); +console.log(Buffer.concat(chunks, 6)); +console.log(Buffer.concat(chunks, 3).toString()); +console.log(Buffer.concat([]).length); + +const lists = [[1], ["a"], [Buffer.from("x"), null], "x", 5, [new Uint16Array(1)]]; +for (const list of lists) { + try { + Buffer.concat(list); + console.log("no error"); + } catch (e) { + console.log(e.name, e.code, e.message); + } +} + +const withLength = [ + [[1], -1], + [[Buffer.from("a"), "b"], NaN], + [[1], 2], + [[], -1], + [[Buffer.from("a")], 1.5], + [[Buffer.from("a"), Buffer.from("b")], 2], +]; +for (const [list, length] of withLength) { + try { + console.log("length", Buffer.concat(list, length).length); + } catch (e) { + console.log(e.name, e.code, e.message); + } +} diff --git a/tests/corpus/5976-builtin-class-aliases.ts b/tests/corpus/5976-builtin-class-aliases.ts new file mode 100644 index 000000000..692b3928d --- /dev/null +++ b/tests/corpus/5976-builtin-class-aliases.ts @@ -0,0 +1,109 @@ +// Builtin classes through const aliases: EventEmitter, the stream classes, +// the error classes, Map and Set construct, extend and test instanceof +// through an alias, and typeof, .name and === answer as in Node for the +// aliases and the direct spellings. +import { EventEmitter } from "node:events"; +import * as events from "node:events"; +import { PassThrough, Readable, Transform } from "node:stream"; +import * as stream from "node:stream"; +import { AssertionError } from "node:assert"; +import * as assert from "node:assert"; + +function lateEmitter(): boolean { + return new LateEmitter() instanceof EventEmitter && typeof LateMap === "function"; +} + +function localHoist(): string { + function inner(): string { + return `${typeof LocalMap} ${LocalMap.name} ${new LocalMap().size}`; + } + const LocalMap = Map; + return inner(); +} + +const Emitter = EventEmitter; +const NsEmitter = events.EventEmitter; +const Again = Emitter; +const LateEmitter = events.EventEmitter; +const LateMap = Map; + +const e = new Emitter(); +e.on("ping", (n: number) => console.log("ping", n)); +e.emit("ping", 1); +console.log("emitter", e instanceof Emitter, e instanceof NsEmitter, e instanceof Again); +console.log("hoisted use", lateEmitter(), localHoist()); + +class Bus extends Again { + send(text: string): void { + this.emit("text", text); + } +} +const bus = new Bus(); +bus.on("text", (text: string) => console.log("bus", text)); +bus.send("hello"); +console.log("subclass", bus instanceof Emitter, bus instanceof Bus); + +const Pass = PassThrough; +const R = Readable; +const T = stream.Transform; +const pass = new Pass(); +console.log("stream", pass instanceof Pass, pass instanceof R, pass instanceof T); +console.log("readable", new R({ read() {} }) instanceof Pass, e instanceof R); + +class Upper extends T { + override _transform( + chunk: Buffer, + _encoding: BufferEncoding, + callback: (error: Error | null, data?: string) => void, + ): void { + callback(null, chunk.toString().toUpperCase()); + } +} +const upper = new Upper(); +upper.on("data", (chunk: Buffer) => console.log("upper", chunk.toString())); +upper.end("quiet"); + +const TE = TypeError; +const RE = RangeError; +class LimitError extends RE { + constructor(limit: number) { + super(`over ${limit}`); + this.name = "LimitError"; + } +} +try { + throw new TE("bad input"); +} catch (err) { + console.log("error", err instanceof TE, err instanceof RE, err instanceof Error, (err as Error).message); +} +try { + throw new LimitError(3); +} catch (err) { + console.log("subclass error", err instanceof RE, err instanceof LimitError, (err as Error).message); +} +const AE = AssertionError; +try { + assert.strictEqual(1, 2); +} catch (err) { + console.log("assertion", err instanceof AE, err instanceof TE); +} +const viaNamespace = new assert.AssertionError({ message: "custom" }); +console.log("namespace error", viaNamespace.message, viaNamespace instanceof AE); + +const M = Map; +const S = Set; +const m = new M([ + ["a", 1], + ["b", 2], +]); +m.set("c", 3); +const s = new S([1, 2, 2, 3]); +console.log("collections", m.get("b"), m.size, s.size, m instanceof M, s instanceof S, s instanceof M); +const groups = M.groupBy([1, 2, 3, 4, 5], (n: number) => (n % 2 === 0 ? "even" : "odd")); +console.log("groupBy", groups.get("even"), groups.get("odd")); + +console.log("typeof", typeof Emitter, typeof R, typeof TE, typeof M, typeof EventEmitter, typeof Readable); +console.log("name", Emitter.name, NsEmitter.name, Pass.name, T.name, TE.name, AE.name, M.name, S.name); +console.log("direct name", EventEmitter.name, stream.Duplex.name, Error.name, RangeError.name, Map.name, Set.name); +console.log("identity", Emitter === EventEmitter, Again === NsEmitter, T === Transform, R === stream.Readable); +console.log("other class", (Pass as unknown) === (R as unknown), (M as unknown) !== (S as unknown), (TE as unknown) === (RE as unknown)); diff --git a/tests/corpus/5977-builtin-class-aliases.cjs b/tests/corpus/5977-builtin-class-aliases.cjs new file mode 100644 index 000000000..c149884ba --- /dev/null +++ b/tests/corpus/5977-builtin-class-aliases.cjs @@ -0,0 +1,45 @@ +// Builtin classes through const aliases in a CommonJS file. A copy of a +// name (or of a globalThis property) keeps its value, so it can be stored +// and compared; construction, instanceof, typeof and .name resolve to the +// class. Builtin module members construct through the alias. +const { EventEmitter } = require("node:events"); +const events = require("node:events"); +const zlib = require("node:zlib"); +const { PassThrough } = require("node:stream"); + +const Emitter = EventEmitter; +const NsEmitter = events.EventEmitter; +const Gz = zlib.Gzip; +const Gz2 = Gz; +const M = Map; +const GlobalSet = globalThis.Set; +const Pass = PassThrough; +const TE = TypeError; + +const e = new Emitter(); +e.on("ping", (n) => console.log("ping", n)); +e.emit("ping", 2); +console.log("emitter", e instanceof Emitter, e instanceof NsEmitter, e instanceof EventEmitter); + +const m = new M(); +m.set("k", 1); +const s = new GlobalSet(["a", "b", "a"]); +console.log("collections", m.get("k"), s.size, m instanceof M, s instanceof GlobalSet, m instanceof GlobalSet); + +const gz = new Gz2(); +console.log("zlib", gz instanceof Gz, gz instanceof zlib.Gunzip, Gz2 === zlib.Gzip); + +const pass = new Pass(); +console.log("stream", pass instanceof Pass, pass instanceof EventEmitter); + +try { + throw new TE("typed"); +} catch (err) { + console.log("error", err instanceof TE, err.message); +} + +const stored = [Emitter, M, GlobalSet, Pass]; +console.log("stored", stored.length, stored[0] === EventEmitter, stored[1] === Map, stored[2] === Set); +console.log("typeof", typeof Emitter, typeof NsEmitter, typeof Gz2, typeof M, typeof GlobalSet, typeof TE); +console.log("name", Emitter.name, NsEmitter.name, Gz2.name, M.name, GlobalSet.name, Pass.name, TE.name); +console.log("identity", Emitter === NsEmitter, M === Map, GlobalSet === Set, M === GlobalSet); diff --git a/tests/corpus/5978-builtin-class-alias-modules/classes.ts b/tests/corpus/5978-builtin-class-alias-modules/classes.ts new file mode 100644 index 000000000..73590d1e8 --- /dev/null +++ b/tests/corpus/5978-builtin-class-alias-modules/classes.ts @@ -0,0 +1,8 @@ +import { EventEmitter } from "node:events"; +import { PassThrough } from "node:stream"; + +export const Emitter = EventEmitter; +export const Pass = PassThrough; +export const Dict = Map; +export const Bag = Set; +export const Failure = TypeError; diff --git a/tests/corpus/5978-builtin-class-alias-modules/main.ts b/tests/corpus/5978-builtin-class-alias-modules/main.ts new file mode 100644 index 000000000..cf39cd712 --- /dev/null +++ b/tests/corpus/5978-builtin-class-alias-modules/main.ts @@ -0,0 +1,33 @@ +// Builtin class aliases exported from one module and used through named +// and namespace imports: construction, instanceof, typeof, .name and === +// resolve to the builtin class in the importing module. +import { EventEmitter } from "node:events"; +import { Emitter, Pass, Dict, Bag, Failure } from "./classes.ts"; +import * as classes from "./classes.ts"; + +const e = new Emitter(); +e.on("tick", (n: number) => console.log("tick", n)); +e.emit("tick", 7); +console.log("emitter", e instanceof Emitter, e instanceof classes.Emitter, e instanceof EventEmitter); + +const viaNs = new classes.Emitter(); +console.log("namespace emitter", viaNs instanceof Emitter, Emitter === classes.Emitter); + +const pass = new classes.Pass(); +console.log("stream", pass instanceof Pass, pass instanceof classes.Emitter); + +const dict = new Dict([["k", 1]]); +const viaNsDict = new classes.Dict(); +viaNsDict.set("a", "b"); +const bag = new classes.Bag([1, 1, 2]); +console.log("collections", dict.get("k"), viaNsDict.get("a"), bag.size, dict instanceof classes.Dict, bag instanceof Bag); + +try { + throw new classes.Failure("wrong type"); +} catch (err) { + console.log("error", err instanceof Failure, err instanceof classes.Failure, (err as Error).message); +} + +console.log("typeof", typeof Emitter, typeof classes.Pass, typeof Dict, typeof classes.Bag, typeof Failure); +console.log("name", Emitter.name, classes.Pass.name, Dict.name, classes.Bag.name, Failure.name); +console.log("identity", Dict === Map, classes.Bag === Set, Failure === TypeError, (Dict as unknown) === (Bag as unknown)); diff --git a/tests/corpus/5979-writable-instanceof-duplex.ts b/tests/corpus/5979-writable-instanceof-duplex.ts new file mode 100644 index 000000000..f525004ab --- /dev/null +++ b/tests/corpus/5979-writable-instanceof-duplex.ts @@ -0,0 +1,75 @@ +// `x instanceof Writable` answers true for Duplex streams and their +// subclasses, as Node's Writable[Symbol.hasInstance] does. Subclasses of +// Writable keep the prototype test. +import { Duplex, PassThrough, Readable, Transform, Writable } from "node:stream"; +import * as zlib from "node:zlib"; +import { EventEmitter } from "node:events"; + +class Sink extends Writable { + override _write(_chunk: Buffer, _encoding: BufferEncoding, callback: () => void): void { + callback(); + } +} +class Pipe extends Duplex { + override _read(): void {} + override _write(_chunk: Buffer, _encoding: BufferEncoding, callback: () => void): void { + callback(); + } +} +class Upper extends Transform { + override _transform( + chunk: Buffer, + _encoding: BufferEncoding, + callback: (error: Error | null, data?: string) => void, + ): void { + callback(null, chunk.toString().toUpperCase()); + } +} + +const writable = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, +}); +const duplex = new Duplex({ + read() {}, + write(_chunk, _encoding, callback) { + callback(); + }, +}); +const pass = new PassThrough(); +const transform = new Transform({ + transform(chunk, _encoding, callback) { + callback(null, chunk); + }, +}); +const readable = new Readable({ read() {} }); + +console.log("writable", writable instanceof Writable, new Sink() instanceof Writable); +console.log( + "duplex family", + duplex instanceof Writable, + pass instanceof Writable, + transform instanceof Writable, + new Pipe() instanceof Writable, + new Upper() instanceof Writable, + zlib.createGzip() instanceof Writable, +); +console.log("readable", readable instanceof Writable, readable instanceof Duplex); +console.log("subclass of writable", pass instanceof Sink, duplex instanceof Sink, new Sink() instanceof Sink); + +const asReadable: Readable = pass; +const asEmitter: EventEmitter = new Pipe(); +const plainEmitter: EventEmitter = new EventEmitter(); +console.log("base-typed", asReadable instanceof Writable, asEmitter instanceof Writable, plainEmitter instanceof Writable); + +function describe(value: Readable | string): string { + return value instanceof Writable ? "writable side" : "no writable side"; +} +console.log("union", describe(duplex), describe(readable), describe("text")); + +try { + throw transform; +} catch (err) { + console.log("caught", err instanceof Writable, err instanceof Readable); +} diff --git a/tests/corpus/5980-cycle-components.ts b/tests/corpus/5980-cycle-components.ts new file mode 100644 index 000000000..05e8ffed1 --- /dev/null +++ b/tests/corpus/5980-cycle-components.ts @@ -0,0 +1,101 @@ +// Collector passes whose candidates mix dead structures with live ones: the +// ring under construction when a pass runs, owners that keep an inner node +// of an otherwise dead ring, trees whose child arrays have holes and sparse +// slots, and a live structure too large for a pass to record. Each dead +// structure is reclaimed whole, and everything still reachable survives +// with its contents intact. The sanitized lane's RC audit fails if anything +// below leaks or is freed while reachable. + +class Node { + next: Node | null = null; + kids: (Node | undefined)[] = []; + owner: Node | null = null; + value: number; + constructor(value: number) { + this.value = value; + } +} + +function ring(size: number, base: number): Node { + const first = new Node(base); + let last = first; + for (let i = 1; i < size; i++) { + const node = new Node(base + i); + last.next = node; + last = node; + } + last.next = first; + return first; +} + +function tree(depth: number, owner: Node | null, value: number): Node { + const node = new Node(value); + node.owner = owner; + if (depth > 0) for (let i = 0; i < 3; i++) node.kids.push(tree(depth - 1, node, value * 3 + i)); + return node; +} + +function total(start: Node): number { + const seen = new Set(); + const stack: Node[] = [start]; + let sum = 0; + while (stack.length > 0) { + const node = stack.pop()!; + if (seen.has(node)) continue; + seen.add(node); + sum = (sum + node.value) % 1000003; + if (node.next) stack.push(node.next); + if (node.owner) stack.push(node.owner); + for (const kid of node.kids) if (kid) stack.push(kid); + } + return sum * 1000 + (seen.size % 1000); +} + +// Dead rings dropped while the next one is being built; a few are kept. +let rings = 0; +const kept: Node[] = []; +for (let round = 0; round < 3000; round++) { + const r = ring(5 + (round % 7), round); + rings = (rings + r.next!.value) % 1000003; + if (round % 500 === 0) kept.push(r); +} +console.log("rings", rings, kept.map(total).join(" ")); + +// Rings whose head is dropped while an owner keeps a node in the middle: +// the ring stays reachable through it and must survive whole. +const anchors: Node[] = []; +for (let round = 0; round < 400; round++) { + let mid = ring(30, round * 100); + for (let i = 0; i < 15; i++) mid = mid.next!; + if (round % 4 === 0) anchors.push(mid); +} +let anchored = 0; +for (const anchor of anchors) anchored = (anchored + total(anchor)) % 1000003; +console.log("anchored", anchors.length, anchored); + +// Trees with holes and sparse slots in their child arrays. +let trees = 0; +const shaped: Node[] = []; +for (let round = 0; round < 300; round++) { + const t = tree(4, null, round); + if (round % 3 === 0) t.kids.length = 6; + if (round % 5 === 0) t.kids[200] = tree(2, t, round + 7); + if (round % 7 === 0) t.kids[0]!.kids.length = 1; + trees = (trees + total(t)) % 1000003; + if (round % 60 === 0) shaped.push(t.kids[2]!); +} +console.log("trees", trees, shaped.map(total).join(" ")); + +// A live tree larger than one pass records, built and read while dead +// structures keep dying around it, then dropped. +const big = tree(10, null, 1); +let churn = 0; +for (let round = 0; round < 200; round++) { + const r = ring(50, round); + const branch = big.kids[round % 3]!.kids[(round >> 2) % 3]!; + branch.next = r; + churn = (churn + total(r)) % 1000003; + if (round % 50 === 0) churn = (churn + total(big)) % 1000003; + branch.next = null; +} +console.log("big", churn, total(big)); diff --git a/tests/corpus/5993-array-length-loop-resizing.ts b/tests/corpus/5993-array-length-loop-resizing.ts new file mode 100644 index 000000000..a1d5f6d11 --- /dev/null +++ b/tests/corpus/5993-array-length-loop-resizing.ts @@ -0,0 +1,167 @@ +// Index loops bounded by an array's length whose bodies resize that array, +// directly, through an alias, a helper or a closure: the bound is re-read on +// every pass, exactly like Node. + +function pushInBody(): string { + const a = [1, 2, 3]; + const seen: number[] = []; + for (let i = 0; i < a.length; i++) { + seen.push(a[i]); + if (a.length < 6) a.push(a[i] * 10); + } + return `${seen.join(",")} | ${a.length}`; +} + +function stridedPush(): string { + const a = [0, 1, 2, 3]; + let visits = 0; + for (let i = 0; i < a.length; i += 2) { + visits++; + if (a.length < 9) a.push(i); + } + return `${visits} ${a.join(",")}`; +} + +function truncateInBody(): string { + const a = [5, 6, 7, 8, 9, 10]; + let sum = 0; + for (let i = 0; i < a.length; i++) { + sum += a[i]; + if (i === 2) a.length = 4; + } + return `${sum} ${a.length}`; +} + +function growLengthInBody(): string { + const a = [1, 2]; + let holes = 0; + for (let i = 0; i < a.length; i++) { + if (a[i] === undefined) holes++; + if (i === 0) a.length = 5; + } + return `${holes} ${a.length}`; +} + +function helperPush(xs: number[], v: number): void { + if (xs.length < 7) xs.push(v); +} + +function pushThroughHelper(): string { + const a = [1, 2, 3]; + let sum = 0; + for (let i = 0; i < a.length; i++) { + sum += a[i]; + helperPush(a, i + 100); + } + return `${sum} ${a.length}`; +} + +function pushThroughClosure(): string { + const a = [1, 2, 3]; + const grow = (v: number): void => { + if (a.length < 6) a.push(v); + }; + let sum = 0; + for (let i = 0; i < a.length; i++) { + sum += a[i]; + grow(i); + } + return `${sum} ${a.length}`; +} + +function pushThroughAlias(): string { + const a = [1, 2, 3]; + const alias = a; + let sum = 0; + for (let i = 0; i < a.length; i++) { + sum += a[i]; + if (alias.length < 8) alias.push(i * 2); + } + return `${sum} ${a.length}`; +} + +function aliasedParameters(a: number[], out: number[]): string { + // `out` may be `a` itself. + let n = 0; + for (let i = 0; i < a.length; i++) { + n++; + if (out.length < 10) out.push(a[i]); + } + return `${n} ${a.length} ${out.length}`; +} + +function writePastEnd(): string { + const a = [1, 2, 3]; + let passes = 0; + for (let i = 0; i < a.length; i++) { + passes++; + if (i + 1 === a.length && a.length < 6) a[i + 1] = i; + } + return `${passes} ${a.join(",")}`; +} + +function shiftInBody(): string { + const a = [1, 2, 3, 4, 5, 6]; + const seen: number[] = []; + for (let i = 0; i < a.length; i++) { + seen.push(a[i]); + a.shift(); + } + return `${seen.join(",")} | ${a.join(",")}`; +} + +function spliceInDescendingLoop(): string { + const a = [1, 2, 3, 4, 5, 6, 7]; + for (let i = a.length - 1; i >= 0; i--) if (a[i] % 2 === 0) a.splice(i, 1); + return a.join(","); +} + +function whilePush(): string { + const a = [3, 1]; + let i = 0; + let sum = 0; + while (i < a.length) { + sum += a[i]; + if (a.length < 5) a.push(sum); + i++; + } + return `${sum} ${i} ${a.join(",")}`; +} + +function nestedResize(): string { + const rows = [[1], [2, 3], [4, 5, 6]]; + let total = 0; + for (let r = 0; r < rows.length; r++) { + const row = rows[r]; + for (let c = 0; c < row.length; c++) { + total += row[c]; + if (row.length < 3) row.push(c); + } + } + return `${total} ${rows.map((row) => row.length).join(",")}`; +} + +function pushIntoOther(): string { + const a = [2, 4, 6]; + const out: number[] = []; + for (let i = 0; i < a.length; i++) out.push(a[i] + 1); + for (let i = 0; i < out.length; i += 2) out.push(-1); + return `${out.join(",")}`; +} + +console.log(pushInBody()); +console.log(stridedPush()); +console.log(truncateInBody()); +console.log(growLengthInBody()); +console.log(pushThroughHelper()); +console.log(pushThroughClosure()); +console.log(pushThroughAlias()); +const shared = [7, 8, 9]; +console.log(aliasedParameters(shared, shared)); +console.log(aliasedParameters([7, 8, 9], [])); +console.log(writePastEnd()); +console.log(shiftInBody()); +console.log(spliceInDescendingLoop()); +console.log(whilePush()); +console.log(nestedResize()); +console.log(pushIntoOther()); diff --git a/tests/corpus/5994-array-length-sparse-loops.ts b/tests/corpus/5994-array-length-sparse-loops.ts new file mode 100644 index 000000000..589c1df60 --- /dev/null +++ b/tests/corpus/5994-array-length-sparse-loops.ts @@ -0,0 +1,133 @@ +// Index loops over sparse arrays with lengths beyond 2^31, holes, and arrays +// that become sparse while a loop runs. Dense-loop fast paths must leave all +// of these on the general path. + +function partialAscending(a: number[], limit: number): string { + let sum = 0; + let passes = 0; + for (let i = 0; i < a.length; i++) { + if (passes++ === limit) break; + sum += a[i] === undefined ? -1 : a[i]; + } + return `${passes} ${sum}`; +} + +function partialDescending(a: number[], limit: number): string { + let last = -1; + let passes = 0; + for (let i = a.length - 1; i >= 0; i--) { + if (passes++ === limit) break; + last = i; + } + return `${passes} ${last}`; +} + +function stridedFromEnd(a: number[]): string { + const hits: number[] = []; + for (let i = a.length - 1; i >= 0 && hits.length < 3; i -= 1000000000) hits.push(i); + return hits.join(","); +} + +function whileWithLimit(a: number[], limit: number): number { + let i = 0; + let present = 0; + while (i < a.length && i < limit) { + if (a[i] !== undefined) present++; + i++; + } + return present * 1000 + i; +} + +function writeAtCounter(a: number[], limit: number): string { + for (let i = 0; i < a.length && i < limit; i++) a[i] = i * 2; + return `${a.length} ${a[0]} ${a[limit - 1]} ${a[limit]}`; +} + +function holeSum(a: number[]): string { + let sum = 0; + let nan = 0; + for (let i = 0; i < a.length; i++) { + const v = a[i]; + if (Number.isNaN(v + 0)) nan++; + else sum += v; + } + return `${sum} ${nan}`; +} + +function firstValues(a: number[]): string { + let s = 0; + for (let i = 0; i < a.length; i++) { + if (i === 4) break; + s += a[i]; + } + let t = 0; + for (let i = a.length - 1; i >= 0; i--) { + if (i < a.length - 2) break; + t += a[i]; + } + let u = 0; + for (let i = 0; i < a.length; i += 2) { + if (i > 2) break; + u += a[i]; + } + return `${s} ${t} ${u}`; +} + +const huge: number[] = [1, 2, 3]; +huge.length = 3e9; +console.log(huge.length); +console.log(partialAscending(huge, 5)); +console.log(firstValues(huge), firstValues([1, 2, 3, 4, 5])); +console.log(partialDescending(huge, 4)); +console.log(stridedFromEnd(huge)); +console.log(whileWithLimit(huge, 6)); +console.log(writeAtCounter(huge, 4)); +console.log(huge.length); + +const edge: number[] = []; +edge[4294967294] = 7; +console.log(edge.length, partialDescending(edge, 2), partialAscending(edge, 3)); + +const holey = [1, 2]; +holey[3] = 4; +holey[5] = 6; +console.log(holeSum(holey)); +const sparse: number[] = [1, 2]; +sparse[10] = 5; +console.log(holeSum(sparse)); + +// Becomes sparse mid-loop: a write far past the end through another binding. +function sparseMidLoop(): string { + const a = [1, 2, 3, 4]; + const b = a; + let sum = 0; + for (let i = 0; i < a.length; i++) { + if (i === 1) b[3000000000] = 9; + if (i > 6) break; + sum += a[i] === undefined ? 100 : a[i]; + } + return `${sum} ${a.length}`; +} +console.log(sparseMidLoop()); + +function holeMidLoop(): string { + const a = [1, 2, 3, 4, 5]; + let sum = 0; + for (let i = 0; i < a.length; i++) { + if (i === 1) a.length = 3; + if (i === 2) a.length = 5; + sum += a[i] === undefined ? 100 : a[i]; + } + return `${sum}`; +} +console.log(holeMidLoop()); + +const objects: { v: number }[] = [{ v: 1 }, { v: 2 }, { v: 3 }]; +objects.length = 5; +try { + let total = 0; + for (let i = 0; i < objects.length; i++) total += objects[i].v; + console.log(total); +} catch (e) { + console.log(e instanceof TypeError ? "TypeError" : "other"); +} diff --git a/tests/corpus/5995-array-dense-loop-boundaries.ts b/tests/corpus/5995-array-dense-loop-boundaries.ts new file mode 100644 index 000000000..5efad78ec --- /dev/null +++ b/tests/corpus/5995-array-dense-loop-boundaries.ts @@ -0,0 +1,173 @@ +// Index loops whose array length the loop never changes, run over dense, +// holey, partially filled, aliased and short arrays, with fractional, +// negative and offset bounds, ascending, strided and descending counters. + +interface Point { + x: number; + y: number; +} + +function sum(a: number[]): number { + let s = 0; + for (let i = 0; i < a.length; i++) s += a[i]; + return s; +} + +function scale(a: number[], b: number[]): string { + for (let i = 0; i < a.length; i++) b[i] = a[i] * 2; + return `${b.length}:${b.join(",")}`; +} + +function inPlace(a: number[]): string { + for (let i = 0; i < a.length; i++) a[i] = a[i] + 1; + return a.join(","); +} + +function strided(a: number[], step: number): string { + let s = 0; + if (step === 2) for (let i = 0; i < a.length; i += 2) s += a[i]; + else for (let i = 1; i < a.length; i += 3) s += a[i]; + return `${s}`; +} + +function descending(a: number[]): string { + const out: number[] = []; + for (let i = a.length - 1; i >= 0; i--) out.push(a[i]); + return out.join(","); +} + +function descendingPairs(a: number[]): number { + let s = 0; + for (let i = a.length - 2; i > 0; i -= 2) s += a[i] - a[i + 1]; + return s; +} + +function offsetBounds(a: number[]): string { + let inner = 0; + for (let i = 1; i < a.length - 1; i++) inner += a[i]; + let inclusive = 0; + for (let i = 0; i <= a.length - 1; i++) inclusive += a[i]; + let diffs = 0; + for (let i = 0; i < a.length - 1; i++) diffs += a[i + 1] - a[i]; + return `${inner} ${inclusive} ${diffs}`; +} + +function fractionalBounds(a: number[]): string { + const seen: number[] = []; + for (let i = 0.5; i < a.length; i++) seen.push(a[i] === undefined ? -1 : 1); + for (let i = -2; i < a.length - 3; i++) seen.push(a[i] === undefined ? -2 : a[i]); + for (let i = 0; i < a.length - 0.5; i++) seen.push(i); + return seen.join(","); +} + +function whileLoop(a: number[]): number { + let i = 0; + let s = 0; + while (i < a.length) { + s += a[i] * i; + i++; + } + return s; +} + +function constLength(a: number[]): number { + const n = a.length; + let s = 0; + for (let i = 0; i < n; i++) s += a[i]; + return s; +} + +function points(ps: Point[]): string { + let s = 0; + for (let i = 0; i < ps.length; i++) s += ps[i].x * ps[i].y; + return `${s}`; +} + +function flags(bs: boolean[]): number { + let n = 0; + for (let i = 0; i < bs.length; i++) if (bs[i]) n++; + return n; +} + +function names(ss: string[]): string { + let out = ""; + for (let i = ss.length - 1; i >= 0; i--) out += ss[i]; + return out; +} + +function maybe(xs: (number | undefined)[]): string { + let s = 0; + let missing = 0; + for (let i = 0; i < xs.length; i++) { + const v = xs[i]; + if (v === undefined) missing++; + else s += v; + } + return `${s} ${missing}`; +} + +function nested(g: number[][]): number { + let s = 0; + for (let r = 0; r < g.length; r++) { + const row = g[r]; + for (let c = 0; c < row.length; c++) s += row[c] * (r + 1); + } + return s; +} + +function counterAsNumber(a: number[]): string { + let mixed = 0; + for (let i = 0; i < a.length; i++) mixed += i / 2 + (i | 0) + a[i] * -0; + return `${mixed} ${Object.is(mixed, -0)}`; +} + +function earlyExit(a: number[], target: number): number { + for (let i = 0; i < a.length; i++) if (a[i] === target) return i; + return -1; +} + +const dense = [1, 2, 3, 4, 5, 6, 7, 8]; +console.log(sum(dense), sum([]), sum([0.5, -0.25])); +console.log(scale(dense, new Array(8).fill(0))); +console.log(scale(dense, [9, 9])); +console.log(scale([1, 2, 3], [])); +const self = [1, 2, 3, 4]; +console.log(scale(self, self)); +console.log(inPlace([10, 20, 30])); +console.log(strided(dense, 2), strided(dense, 3)); +console.log(descending(dense), descending([])); +console.log(descendingPairs(dense), descendingPairs([1])); +console.log(offsetBounds(dense), offsetBounds([4]), offsetBounds([])); +console.log(fractionalBounds([1, 2, 3, 4, 5])); +console.log(whileLoop(dense)); +console.log(constLength(dense)); +console.log(points([{ x: 1, y: 2 }, { x: 3, y: 4 }])); +console.log(flags([true, false, true, true])); +console.log(names(["a", "b", "c"])); +console.log(maybe([1, undefined, 3])); +console.log(nested([[1, 2], [3], [], [4, 5, 6]])); +console.log(counterAsNumber([1, 2, 3])); +console.log(earlyExit(dense, 5), earlyExit(dense, 50)); + +// Holes and partially filled storage take the checked path. +const holey = [1]; +holey[2] = 3; +holey[4] = 5; +console.log(sum(holey), descending(holey), whileLoop(holey)); +const target = [0, 0, 0]; +target[4] = 0; +console.log(scale(dense.slice(0, 5), target)); +const gap: number[] = []; +gap[3] = 1; +console.log(sum(gap), scale([1, 2, 3, 4], gap)); +const shortened = [1, 2, 3, 4, 5, 6]; +shortened.length = 3; +shortened.length = 6; +console.log(sum(shortened), constLength(shortened)); +const holeyPoints: Point[] = [{ x: 1, y: 1 }]; +holeyPoints[2] = { x: 2, y: 2 }; +try { + console.log(points(holeyPoints)); +} catch (e) { + console.log(e instanceof TypeError ? "TypeError" : "other"); +} diff --git a/tests/corpus/5996-rejections-between-macrotasks.cjs b/tests/corpus/5996-rejections-between-macrotasks.cjs new file mode 100644 index 000000000..1afd8c8e8 --- /dev/null +++ b/tests/corpus/5996-rejections-between-macrotasks.cjs @@ -0,0 +1,33 @@ +'use strict'; +// Unhandled rejections are reported after the callback that left them, +// before the next due timer or the next immediate runs (Node's +// runNextTicks between macrotasks), also when several timers expire in +// the same turn. +process.on('unhandledRejection', (reason) => { + console.log('unhandledRejection', reason instanceof Error ? reason.message : JSON.stringify(reason)); +}); +process.on('uncaughtException', (err, origin) => console.log(origin, err)); + +const start = Date.now(); +setTimeout(() => void Promise.reject({ a: 1 }), 1); +setTimeout(() => void Promise.reject(new TypeError('typed reason')), 2); +setTimeout(() => { + throw 'thrown string'; +}, 3); +setTimeout(() => { + console.log('timer 4'); + Promise.reject('handled later').catch((e) => console.log('caught', e)); +}, 4); +setTimeout(() => console.log('timer 5'), 5); +// Every timer above is due once this returns. +while (Date.now() - start < 20) {} + +setTimeout(() => { + setImmediate(() => void Promise.reject('first immediate')); + setImmediate(() => { + console.log('second immediate'); + process.nextTick(() => console.log('tick')); + void Promise.reject('second immediate'); + }); + setImmediate(() => console.log('third immediate')); +}, 30); diff --git a/tests/corpus/5997-frame-records-in-record-unions.ts b/tests/corpus/5997-frame-records-in-record-unions.ts new file mode 100644 index 000000000..c066c3edb --- /dev/null +++ b/tests/corpus/5997-frame-records-in-record-unions.ts @@ -0,0 +1,88 @@ +// Records that are arms of a union of records and also live in frame +// storage. An arm of such a union carries a hidden shape word before its +// fields, so every field address has to skip it: the reference fields a +// frame record owns are read, overwritten and released through the same +// slots as heap records of the shape. The same shapes also flow through +// the union, are compared by identity and are serialized. +interface Ident { + kind: "ident"; + name: string; + tags: string[]; +} +interface Call { + kind: "call"; + callee: string; + args: Ident[]; +} +type Node = Ident | Call; + +const words = ["alpha", "beta", "gamma", "delta", "epsilon"]; +function word(i: number): string { + return words[i % words.length]! + i; +} + +function describe(node: Node): string { + switch (node.kind) { + case "ident": + return `${node.name}[${node.tags.join(",")}]`; + case "call": + return `${node.callee}(${node.args.map((a) => a.name).join(", ")})`; + } +} +function weight(node: Node): number { + return node.kind === "ident" ? node.name.length + node.tags.length : node.args.length * 10; +} + +// Heap values of the union: the arms are tagged shapes. +const nodes: Node[] = []; +for (let i = 0; i < 6; i++) { + const id: Ident = { kind: "ident", name: word(i), tags: [word(i + 1)] }; + nodes.push(i % 2 === 0 ? id : { kind: "call", callee: word(i + 2), args: [id, id] }); +} +console.log(nodes.map(describe).join(" | ")); + +// Frame records of the arms that only flow into field reads. +let total = 0; +let log = ""; +for (let i = 0; i < 10; i++) { + const w = word(i); + const id: Ident = { kind: "ident", name: w + "!", tags: [w, word(i + 3)] }; + total += id.name.length + id.tags.length + id.tags[1]!.length; + const call: Call = { kind: "call", callee: w.toUpperCase(), args: [] }; + total += call.callee.length + call.args.length; + log += id.kind[0]! + call.kind[0]! + id.name[0]!; +} +console.log(total, log); + +// A reassigned frame binding: each new value is built apart and copied over +// the slot, and the replaced reference fields are released. +let cur: Ident = { kind: "ident", name: word(0), tags: [] }; +for (let i = 1; i < 8; i++) { + cur = { kind: "ident", name: cur.name.slice(0, 3) + word(i), tags: [...cur.tags, cur.name] }; + if (i % 3 === 0) cur = { kind: "ident", name: cur.tags[0] ?? "none", tags: cur.tags.slice(1) }; +} +console.log(cur.name, cur.tags.length, cur.tags.join("+")); + +// Field writes into a frame record's reference slots. +for (let i = 0; i < 4; i++) { + const id: Ident = { kind: "ident", name: word(i), tags: [word(i)] }; + id.name = id.name + "/" + i; + id.tags = [id.name, ...id.tags]; + id.tags.push(word(i + 7)); + console.log(id.name, id.tags.join(" ")); +} + +// Through the union: describe and weigh by union, keep identity, and +// serialize the arms and the union values. +const a: Ident = { kind: "ident", name: "x", tags: ["t1", "t2"] }; +const b: Call = { kind: "call", callee: "f", args: [a] }; +const asNode: Node = a; +const picked: Node = weight(b) > weight(a) ? b : a; +console.log(describe(asNode), describe(b), weight(asNode), weight(picked)); +console.log(asNode === a, picked === b, b.args[0] === a, (picked as Call).args[0] === asNode); +a.tags.push("t3"); +console.log(describe(asNode), describe(picked)); +console.log(JSON.stringify(a), JSON.stringify(picked)); +console.log(JSON.stringify(nodes.slice(0, 2))); +const mixed: Node[] = [a, b, nodes[1]!]; +console.log(mixed.filter((n) => n.kind === "ident").length, mixed.indexOf(asNode), mixed.indexOf(b)); diff --git a/tests/corpus/6000-json-stringify-class-instances.ts b/tests/corpus/6000-json-stringify-class-instances.ts new file mode 100644 index 000000000..cc72a0b82 --- /dev/null +++ b/tests/corpus/6000-json-stringify-class-instances.ts @@ -0,0 +1,143 @@ +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + norm(): number { + return Math.hypot(this.x, this.y); + } +} + +class Account { + static count = 0; + #secret = "hidden"; + private balance = 10; + protected owner = "ada"; + label: string | undefined = undefined; + tags: string[] = ["a", "b"]; + get secret(): string { + return this.#secret; + } +} + +class Stamp { + at = 5; + toJSON(key: string): string { + return `stamp(${key})`; + } +} + +class Base { + kind = "base"; + toJSON(): { kind: string; via: string } { + return { kind: this.kind, via: "Base.toJSON" }; + } +} +class Child extends Base { + extra = 1; +} + +class Labeled extends Point { + name = "p"; +} + +class Box { + value: T; + constructor(value: T) { + this.value = value; + } +} + +class Registry { + entries = new Map([["a", 1]]); + count = 2; +} + +class Cycle { + name = "loop"; + next: Cycle | null = null; +} + +class Big { + n = 10n; +} + +const tag = Symbol("tag"); + +class Handlers { + name = "h"; + onEvent = (n: number): number => n + 1; + payload: unknown = { a: [1, "two", null] }; + [tag] = "symbol-keyed"; +} + +class Failure extends Error { + code = "E_FAIL"; +} + +class Empty {} + +class Tagged { + s = new Set([1, 2]); + count = 2; +} + +const p = new Point(3, 4); +console.log(JSON.stringify(p)); +console.log(JSON.stringify(new Account())); +console.log(JSON.stringify(new Stamp())); +console.log(JSON.stringify({ first: new Stamp(), list: [new Stamp()] })); +console.log(JSON.stringify(new Child())); +console.log(JSON.stringify(new Labeled(1, 2))); +console.log(JSON.stringify(new Box("text")), JSON.stringify(new Box(new Point(0, 1)))); +console.log(JSON.stringify(new Registry())); +console.log(JSON.stringify([p, null, new Point(5, 6)])); +console.log(JSON.stringify({ point: p, points: [p, p] }, null, 2)); +console.log(JSON.stringify(p, null, "--")); +console.log(JSON.stringify(p, ["x"])); +console.log(JSON.stringify(p, (key: string, value: unknown) => (key === "y" ? undefined : value))); + +const maybe: Point | null = Math.random() < 2 ? p : null; +console.log(JSON.stringify(maybe)); + +const loop = new Cycle(); +loop.next = loop; +try { + JSON.stringify(loop); + console.log("no error"); +} catch (e) { + console.log(e instanceof TypeError); +} + +try { + JSON.stringify(new Big()); + console.log("no error"); +} catch (e) { + console.log(e instanceof TypeError); +} + +p.x = 30; +console.log(JSON.stringify(p)); + +const h = new Handlers(); +console.log(JSON.stringify(h)); +console.log(JSON.stringify(new Empty())); +console.log(JSON.stringify(new Failure("boom"))); +h.payload = 7; +console.log(JSON.stringify({ handlers: [h] })); +const u: unknown = new Map([["a", 1]]); +console.log(JSON.stringify(new Tagged()), JSON.stringify(u)); + +// Date fields write their toJSON text, also in nested instances. +class Schedule { + name = "weekly"; + next = new Date(Date.UTC(2024, 0, 1)); +} +class Calendar { + title = "team"; + schedules: Schedule[] = [new Schedule()]; +} +console.log(JSON.stringify(new Schedule())); +console.log(JSON.stringify({ calendar: new Calendar() })); diff --git a/tests/corpus/6001-json-stringify-js-class-instances.js b/tests/corpus/6001-json-stringify-js-class-instances.js new file mode 100644 index 000000000..d270a632f --- /dev/null +++ b/tests/corpus/6001-json-stringify-js-class-instances.js @@ -0,0 +1,49 @@ +class Box { + constructor(v) { + this.v = v; + } +} + +class Plain {} + +class Late { + constructor() { + this.a = 1; + } + setB() { + this.b = 2; + } + toString() { + return "late"; + } +} + +class Shaped { + constructor(n) { + this.n = n; + } + toJSON() { + return { shaped: this.n }; + } +} + +const b = new Box(1); +console.log(JSON.stringify(b)); +b.extra = "e"; +console.log(JSON.stringify(b)); + +const q = new Plain(); +console.log(JSON.stringify(q)); +q.a = 1; +q.inner = new Box(2); +q.list = [new Box(3), null]; +console.log(JSON.stringify(q)); +console.log(JSON.stringify([q, b], null, 1)); + +const late = new Late(); +console.log(JSON.stringify(late)); +late.c = 3; +late.setB(); +console.log(JSON.stringify(late)); + +console.log(JSON.stringify({ s: new Shaped(4), arr: [new Shaped(5)] })); diff --git a/tests/corpus/6051-generic-class-factory-results.ts b/tests/corpus/6051-generic-class-factory-results.ts new file mode 100644 index 000000000..542619946 --- /dev/null +++ b/tests/corpus/6051-generic-class-factory-results.ts @@ -0,0 +1,111 @@ +// Classes extending the result of a generic class factory, and the +// factory's class values and instances: each instantiation of the factory +// has its own class, as each call does in Node. +function base(initial: T) { + return class { + value: T = initial; + read(): T { + return this.value; + } + describe(): string { + return `value=${String(this.value)}`; + } + }; +} + +class Counter extends base(42) { + bump(): number { + this.value += 1; + return this.value; + } +} +const counter = new Counter(); +console.log(counter.value, counter.bump(), counter.read(), counter.describe()); + +class Label extends base("hello") { + shout(): string { + return this.read().toUpperCase(); + } + describe(): string { + return `label ${super.describe()}`; + } +} +const label = new Label(); +console.log(label.shout(), label.describe(), label.value.length); + +const NumberBox = base(7); +const box = new NumberBox(); +console.log(box.read() * 2, box instanceof NumberBox, counter instanceof NumberBox); +console.log(base(1) === base(1), typeof NumberBox); +console.log(Counter.name, Label.name, counter instanceof Counter); + +function pair(first: A, second: B) { + return class { + first: A = first; + second: B = second; + swap(): [B, A] { + return [this.second, this.first]; + } + }; +} +class Entry extends pair("id", 3) { + label: string; + constructor(label: string) { + super(); + this.label = label; + } + render(): string { + const [n, s] = this.swap(); + return `${this.label}:${s}:${n * 10}`; + } +} +console.log(new Entry("row").render(), new Entry("col").first); + +interface Shape { + name: string; + size: number; +} +function model(defaults: S) { + return class { + data: S; + constructor(data?: S) { + this.data = data ?? defaults; + } + get(): S { + return this.data; + } + }; +} +class ShapeModel extends model({ name: "square", size: 2 }) { + area(): number { + return this.data.size * this.data.size; + } +} +const square = new ShapeModel(); +const big = new ShapeModel({ name: "big", size: 5 }); +console.log(square.get().name, square.area(), big.get().name, big.area()); + +function tagged(tag: string, payload: T) { + class Tagged { + tag = tag; + payload = payload; + toString(): string { + return `${this.tag}(${String(this.payload)})`; + } + } + return new Tagged(); +} +const first = tagged("a", 1); +const second = tagged("b", true); +console.log(first.toString(), second.toString(), first.payload + 1, second.payload === true); + +const arrowFactory = (initial: T) => + class { + item: T = initial; + }; +class FromArrow extends arrowFactory("arrow") {} +function wrap(value: U): U { + const Made = base(value); + return new Made().read(); +} +console.log(new FromArrow().item, wrap("w"), wrap(9), wrap([1, 2])); diff --git a/tests/corpus/6100-record-get-or-create.ts b/tests/corpus/6100-record-get-or-create.ts new file mode 100644 index 000000000..807cb303e --- /dev/null +++ b/tests/corpus/6100-record-get-or-create.ts @@ -0,0 +1,119 @@ +// Get-or-create over nested index-signature records without +// noUncheckedIndexedAccess: a missing key reads as undefined when the next +// statement tests the binding for absence, in every spelling of that test, +// at module scope and inside functions. A write with a computed key through +// a binding that may hold a missing element throws Node's TypeError after +// the key and the value evaluated. +type ExportMap = Record>; + +interface Target { + subpath: string; + condition: string; + target: string; +} + +function build(targets: Target[]): { exports: ExportMap; unmapped: string[] } { + const exports: ExportMap = {}; + const unmapped: string[] = []; + for (const t of targets) { + if (t.target === "") { + unmapped.push(t.subpath); + continue; + } + let entry = exports[t.subpath]; + if (entry === undefined) { + entry = {}; + exports[t.subpath] = entry; + } + entry[t.condition] = `./${t.target}`; + entry["types"] = `./${t.target}`; + } + return { exports: exports, unmapped: unmapped }; +} + +const result = build([ + { subpath: ".", condition: "import", target: "src/index.ts" }, + { subpath: ".", condition: "require", target: "src/index.ts" }, + { subpath: "./x", condition: "default", target: "" }, + { subpath: "./y", condition: "node", target: "src/y.ts" }, +]); +console.log(JSON.stringify(result.exports), result.unmapped.join(",")); + +function group(words: string[]): Record { + const byInitial: Record = {}; + for (const w of words) { + const k = w.slice(0, 1); + let list = byInitial[k]; + if (list == null) { + list = []; + byInitial[k] = list; + } + list.push(w); + } + return byInitial; +} +console.log(JSON.stringify(group(["apple", "avocado", "banana", "blueberry", "cherry"]))); + +function count(words: string[]): Record { + const counts: Record = {}; + for (const w of words) { + const n = counts[w]; + if (typeof n === "undefined") counts[w] = 1; + else counts[w] = n + 1; + } + return counts; +} +console.log(JSON.stringify(count(["a", "b", "a", "c", "a"]))); + +function lookup(table: Record, key: string): string { + const hit = table[key]; + if (hit !== undefined) return hit; + return `<${key} missing>`; +} +console.log(lookup({ a: "A" }, "a"), lookup({ a: "A" }, "b")); + +function rows(table: Record>, key: string, col: string): number { + const row = table[key]; + if (row != undefined) return row[col] ?? -1; + return -2; +} +console.log(rows({ r: { c: 3 } }, "r", "c"), rows({ r: { c: 3 } }, "r", "d"), rows({}, "x", "c")); + +// Module scope. +const nested: Record> = {}; +const key = process.argv.length > 99 ? "unused" : "k"; +let slot = nested[key]; +if (slot === undefined) { + slot = {}; + nested[key] = slot; +} +slot[key] = 1; +slot["fixed"] = 2; +console.log(JSON.stringify(nested)); + +const names: Record = { present: "yes" }; +const missing = names[key]; +if (!missing) console.log("falsy guard:", missing); +const present = names["present"]; +if (present === void 0) console.log("unreachable"); +else console.log("present:", present); + +// A write with a computed key through a binding that may hold a missing +// array element throws Node's TypeError naming the key, after evaluating +// the key and the value. +function probe(tables: Record[]): void { + const firstTable = tables[0]; + const lastTable = tables[5]; + const pick = (k: string): string => { + console.log("key", k); + return k; + }; + firstTable[pick("b")] = pick("second"); + console.log(JSON.stringify(firstTable)); + try { + lastTable[pick("c")] = pick("value"); + } catch (e) { + console.log((e as Error).name, (e as Error).message); + } +} +probe([{ a: "first" }]); diff --git a/tests/corpus/6101-record-get-or-create-unchecked/main.ts b/tests/corpus/6101-record-get-or-create-unchecked/main.ts new file mode 100644 index 000000000..a2b4ef705 --- /dev/null +++ b/tests/corpus/6101-record-get-or-create-unchecked/main.ts @@ -0,0 +1,70 @@ +// Get-or-create over nested index-signature records under +// noUncheckedIndexedAccess: after the presence guard the binding is still +// stored with its undefined arm, and reads, writes and absence probes with +// computed keys go through it (an internal compiler error before). +type ExportMap = Record>; + +function build(rows: { k: string; c: string; v: string }[]): ExportMap { + const map: ExportMap = {}; + for (const r of rows) { + let entry = map[r.k]; + if (entry === undefined) { + entry = {}; + map[r.k] = entry; + } + entry[r.c] = r.v; + } + return map; +} +console.log( + JSON.stringify( + build([ + { k: "a", c: "x", v: "1" }, + { k: "a", c: "y", v: "2" }, + { k: "b", c: "x", v: "3" }, + ]), + ), +); + +function touch(map: ExportMap, k: string): string { + let e = map[k]; + if (e === undefined) { + e = { [k]: "init" }; + map[k] = e; + } + const before = e[k] ?? "none"; + e[k] = e[k] + "!"; + return before + (k in e ? "+" : "-"); +} +const m: ExportMap = {}; +console.log(touch(m, "a"), touch(m, "a"), touch(m, "b"), JSON.stringify(m)); + +function tally(counts: Record>, group: string, item: string): void { + let row = counts[group]; + if (!row) { + row = {}; + counts[group] = row; + } + row[item] = (row[item] ?? 0) + 1; +} +const counts: Record> = {}; +for (const [g, i] of [ + ["fruit", "apple"], + ["fruit", "pear"], + ["veg", "kale"], + ["fruit", "apple"], +]) { + tally(counts, g!, i!); +} +console.log(JSON.stringify(counts)); + +// Module scope. +const top: ExportMap = {}; +const key = process.argv.length > 99 ? "unused" : "k"; +let slot = top[key]; +if (slot === undefined) { + slot = {}; + top[key] = slot; +} +slot[key] = "v"; +console.log(JSON.stringify(top), slot[key], slot["other"] ?? "missing"); diff --git a/tests/corpus/6101-record-get-or-create-unchecked/tsconfig.json b/tests/corpus/6101-record-get-or-create-unchecked/tsconfig.json new file mode 100644 index 000000000..f2e1c10cd --- /dev/null +++ b/tests/corpus/6101-record-get-or-create-unchecked/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "strict": true, + "noUncheckedIndexedAccess": true + } +} diff --git a/tests/corpus/6103-typeof-builtin-bindings.cjs b/tests/corpus/6103-typeof-builtin-bindings.cjs new file mode 100644 index 000000000..ecef94dd3 --- /dev/null +++ b/tests/corpus/6103-typeof-builtin-bindings.cjs @@ -0,0 +1,15 @@ +// typeof on bindings destructured from Node builtin modules: Node answers by +// the export's kind ("function" for functions and classes, "object" for +// namespace objects), never by how the binding is represented. +const { inspect, format, promisify, deprecate, isDeepStrictEqual } = require("util"); +const { join, resolve } = require("path"); +const { readFileSync, promises } = require("fs"); +const { EventEmitter } = require("events"); +const { createHash } = require("crypto"); +const util = require("node:util"); +const { inspect: aliased } = util; +console.log(typeof inspect, typeof format, typeof promisify, typeof deprecate, typeof isDeepStrictEqual); +console.log(typeof join, typeof resolve, typeof readFileSync, typeof promises); +console.log(typeof EventEmitter, typeof createHash, typeof aliased, typeof util.inspect); +console.log(typeof inspect === "function" ? "callable" : "not callable"); +console.log(join("a", "b"), format("%s=%d", "x", 1), inspect({ a: [1, 2] })); diff --git a/tests/corpus/6104-typeof-builtin-bindings.mjs b/tests/corpus/6104-typeof-builtin-bindings.mjs new file mode 100644 index 000000000..dd2976775 --- /dev/null +++ b/tests/corpus/6104-typeof-builtin-bindings.mjs @@ -0,0 +1,11 @@ +// typeof on named, default-destructured and namespace-destructured builtin +// bindings in an ES module: Node answers by the export's kind. +import util, { inspect as namedInspect, format } from "node:util"; +import * as path from "node:path"; +import { createHash } from "node:crypto"; +import { promises } from "node:fs"; +const { inspect, promisify } = util; +const { join } = path; +console.log(typeof inspect, typeof promisify, typeof namedInspect, typeof format); +console.log(typeof join, typeof createHash, typeof util.inspect, typeof promises); +console.log(namedInspect("s"), format("%d", 2), join("x", "y")); diff --git a/tests/corpus/6105-cjs-top-level-this/lib.js b/tests/corpus/6105-cjs-top-level-this/lib.js new file mode 100644 index 000000000..7addc1840 --- /dev/null +++ b/tests/corpus/6105-cjs-top-level-this/lib.js @@ -0,0 +1,11 @@ +// A required CommonJS module sees its own exports object as `this`, and the +// UMD test for a CommonJS host takes the module.exports branch. +const kind = typeof this; +let host = "global"; +if (typeof module !== "undefined" && module.exports) host = "commonjs"; +(function (root) { + console.log("lib wrapper root:", typeof root, root ? "truthy" : "falsy"); +})(this); +exports.describe = function () { + return kind + " " + host; +}; diff --git a/tests/corpus/6105-cjs-top-level-this/main.js b/tests/corpus/6105-cjs-top-level-this/main.js new file mode 100644 index 000000000..1da910171 --- /dev/null +++ b/tests/corpus/6105-cjs-top-level-this/main.js @@ -0,0 +1,12 @@ +// Top-level `this` in a CommonJS module is module.exports (an object), also +// inside arrow functions at module scope. +const lib = require("./lib.js"); +const self = this; +const arrow = () => this; +console.log(typeof this, self === this, arrow() === this, this ? "truthy" : "falsy"); +console.log(lib.describe()); +(function () { + console.log("call(this):", typeof this); +}).call(this); +if ((typeof module !== "undefined" && module !== null) && module.exports) console.log("commonjs host"); +else console.log("browser host"); diff --git a/tests/corpus/6105-cjs-top-level-this/package.json b/tests/corpus/6105-cjs-top-level-this/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/corpus/6105-cjs-top-level-this/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/corpus/6106-esm-top-level-this.mjs b/tests/corpus/6106-esm-top-level-this.mjs new file mode 100644 index 000000000..6079ecc1e --- /dev/null +++ b/tests/corpus/6106-esm-top-level-this.mjs @@ -0,0 +1,4 @@ +// Top-level `this` in an ES module stays undefined, also inside arrow +// functions at module scope. +const arrow = () => this; +console.log(typeof this, this === undefined, typeof arrow()); diff --git a/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs new file mode 100644 index 000000000..fbb1a65fc --- /dev/null +++ b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs @@ -0,0 +1,30 @@ +// @deferred-fences: 1 +// Function expressions lowered speculatively — the `.call(this)` and +// `.apply` receivers of CommonJS wrappers, and a computed class base — keep +// every statement of their bodies. The wrappers' `root === module.exports` +// 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. +const verbose = process.argv.length > 99; +(function () { + var root = this; + console.log("call wrapper"); + if (verbose) console.log(root === module.exports); + console.log("call wrapper done"); +}).call(this); +(function (label) { + console.log("apply wrapper", label); + if (verbose) console.log(typeof module.exports); +}).apply(null, ["ok"]); +class Base { + hello() { + return "hello"; + } +} +class Derived extends (function () { + if (verbose) console.log(module.exports); + return Base; +})() {} +console.log(new Derived().hello()); diff --git a/tests/corpus/6120-type-only-import-graph/flags.ts b/tests/corpus/6120-type-only-import-graph/flags.ts new file mode 100644 index 000000000..5af639c0a --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/flags.ts @@ -0,0 +1,2 @@ +export type Flag = boolean; +console.log("flags evaluated"); diff --git a/tests/corpus/6120-type-only-import-graph/main.ts b/tests/corpus/6120-type-only-import-graph/main.ts new file mode 100644 index 000000000..251ce6809 --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/main.ts @@ -0,0 +1,21 @@ +// Node's type stripping erases `import type` and `export type … from`, so +// the modules and packages they name are never loaded: a package with no +// runtime entry, a module importing a builtin scriptc does not compile, and +// modules with load-time output. An inline `type` specifier keeps an empty +// import clause and still evaluates its module, as does a mixed clause. +import type { Frozen, Shape } from "types-only-pkg"; +import type { Label } from "./uses-vm.ts"; +import type * as Models from "./models.ts"; +import type { Reexported } from "./reexports.ts"; +import { type Mode, describe } from "./mixed.ts"; +import { type Flag } from "./flags.ts"; + +const shape: Shape = { width: 3, height: 4 }; +const frozen: Frozen = { width: 5, height: 6 }; +const label: Label = "area"; +const model: Models.Model = { id: 1 }; +const payload: Reexported = { ok: true }; +const mode: Mode = "fast"; +const flag: Flag = true; +console.log(label, shape.width * shape.height, model.id, payload.ok); +console.log(describe(mode), flag, frozen.width + frozen.height); diff --git a/tests/corpus/6120-type-only-import-graph/mixed.ts b/tests/corpus/6120-type-only-import-graph/mixed.ts new file mode 100644 index 000000000..ec5a08bbb --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/mixed.ts @@ -0,0 +1,8 @@ +export type { Payload } from "./payload.ts"; + +export type Mode = "fast" | "slow"; + +export function describe(mode: Mode): string { + return `mode=${mode}`; +} +console.log("mixed evaluated"); diff --git a/tests/corpus/6120-type-only-import-graph/models.ts b/tests/corpus/6120-type-only-import-graph/models.ts new file mode 100644 index 000000000..195cf9a17 --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/models.ts @@ -0,0 +1,4 @@ +export interface Model { + id: number; +} +console.log("models evaluated"); diff --git a/tests/corpus/6120-type-only-import-graph/node_modules/types-only-pkg/index.d.ts b/tests/corpus/6120-type-only-import-graph/node_modules/types-only-pkg/index.d.ts new file mode 100644 index 000000000..e9c1b5497 --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/node_modules/types-only-pkg/index.d.ts @@ -0,0 +1,6 @@ +export interface Shape { + width: number; + height: number; +} + +export type Frozen = { readonly [K in keyof T]: T[K] }; diff --git a/tests/corpus/6120-type-only-import-graph/node_modules/types-only-pkg/package.json b/tests/corpus/6120-type-only-import-graph/node_modules/types-only-pkg/package.json new file mode 100644 index 000000000..c2be952b9 --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/node_modules/types-only-pkg/package.json @@ -0,0 +1,5 @@ +{ + "name": "types-only-pkg", + "version": "1.0.0", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6120-type-only-import-graph/payload.ts b/tests/corpus/6120-type-only-import-graph/payload.ts new file mode 100644 index 000000000..beb97c5ac --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/payload.ts @@ -0,0 +1,4 @@ +export interface Payload { + ok: boolean; +} +console.log("payload evaluated"); diff --git a/tests/corpus/6120-type-only-import-graph/reexports.ts b/tests/corpus/6120-type-only-import-graph/reexports.ts new file mode 100644 index 000000000..52e86f294 --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/reexports.ts @@ -0,0 +1,2 @@ +export type { Payload as Reexported } from "./payload.ts"; +console.log("reexports evaluated"); diff --git a/tests/corpus/6120-type-only-import-graph/tsconfig.json b/tests/corpus/6120-type-only-import-graph/tsconfig.json new file mode 100644 index 000000000..aee0ec940 --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "strict": true + } +} diff --git a/tests/corpus/6120-type-only-import-graph/uses-vm.ts b/tests/corpus/6120-type-only-import-graph/uses-vm.ts new file mode 100644 index 000000000..bed6646bf --- /dev/null +++ b/tests/corpus/6120-type-only-import-graph/uses-vm.ts @@ -0,0 +1,4 @@ +import * as vm from "node:vm"; + +export type Label = string; +console.log("uses-vm evaluated", vm.runInThisContext("1 + 1")); diff --git a/tests/corpus/6121-type-specifier-edges/a.ts b/tests/corpus/6121-type-specifier-edges/a.ts new file mode 100644 index 000000000..59d6385da --- /dev/null +++ b/tests/corpus/6121-type-specifier-edges/a.ts @@ -0,0 +1,10 @@ +import { b } from "./b.ts"; + +export interface AInfo { + tag: string; +} + +export function start(): string { + return `a+${b()}`; +} +console.log("a evaluated"); diff --git a/tests/corpus/6121-type-specifier-edges/b.ts b/tests/corpus/6121-type-specifier-edges/b.ts new file mode 100644 index 000000000..a71f62def --- /dev/null +++ b/tests/corpus/6121-type-specifier-edges/b.ts @@ -0,0 +1,7 @@ +import { type AInfo } from "./a.ts"; + +export function b(): string { + const info: AInfo = { tag: "b" }; + return info.tag; +} +console.log("b evaluated"); diff --git a/tests/corpus/6121-type-specifier-edges/config-barrel.ts b/tests/corpus/6121-type-specifier-edges/config-barrel.ts new file mode 100644 index 000000000..dc461f654 --- /dev/null +++ b/tests/corpus/6121-type-specifier-edges/config-barrel.ts @@ -0,0 +1,2 @@ +export { type Config } from "./config.ts"; +console.log("barrel evaluated"); diff --git a/tests/corpus/6121-type-specifier-edges/config.ts b/tests/corpus/6121-type-specifier-edges/config.ts new file mode 100644 index 000000000..1a42a9093 --- /dev/null +++ b/tests/corpus/6121-type-specifier-edges/config.ts @@ -0,0 +1,4 @@ +export interface Config { + name: string; +} +console.log("config evaluated"); diff --git a/tests/corpus/6121-type-specifier-edges/main.ts b/tests/corpus/6121-type-specifier-edges/main.ts new file mode 100644 index 000000000..23855b470 --- /dev/null +++ b/tests/corpus/6121-type-specifier-edges/main.ts @@ -0,0 +1,10 @@ +// An inline `type` specifier keeps its module request under Node's type +// stripping: `export { type X } from` and `import { type X } from` both +// evaluate the named module, once, in source order. A cycle closed only by +// such an import binds nothing at runtime and runs like a side-effect +// import. +import { type Config } from "./config-barrel.ts"; +import { start } from "./a.ts"; + +const config: Config = { name: "demo" }; +console.log("main", config.name, start()); diff --git a/tests/corpus/6122-punycode-conversions.ts b/tests/corpus/6122-punycode-conversions.ts new file mode 100644 index 000000000..061987a78 --- /dev/null +++ b/tests/corpus/6122-punycode-conversions.ts @@ -0,0 +1,45 @@ +// @exit: 1 +// @no-node-shims +// node:punycode's conversions (punycode.js 2.1.0): encode/decode of bare +// labels, toASCII/toUnicode over domains and email addresses (only the part +// after '@' maps; every RFC 3490 separator splits labels), ucs2 code point +// arrays with astral pairs and lone surrogates, and the RangeErrors of +// malformed input. A static import links the module before anything runs, +// so its DEP0040 report precedes the program and no 'warning' listener the +// program registers sees it (the Node side runs without the preload shim, +// which would delay that report past the program's first turn). The report +// carries the process id, so the @exit directive keeps stderr out of the +// comparison. +import * as punycode from "node:punycode"; + +process.on("warning", (w) => console.log("listener", w.name)); + +const show = (label: string, f: () => unknown): void => { + try { + console.log(label, JSON.stringify(f())); + } catch (e) { + const err = e as Error; + console.log(label, "threw", err instanceof RangeError, err.name, err.message); + } +}; + +console.log(punycode.version); +for (const s of ["mañana", "bücher", "ü", "abc", "", "-> $1.00 <-", "ليهمابتكلموشعربي؟", "他们为什么不说中文", "😀x", "MaJiでKoiする5秒前"]) { + show(`encode ${s}`, () => punycode.encode(s)); + show(`roundtrip ${s}`, () => punycode.decode(punycode.encode(s))); +} +for (const s of ["maana-pta", "bcher-kva", "tda", "abc-", "-abc", "ABC-kva", "a-b-c-kva", "ü-kva", "kva-ü", "99999999999", "zzzzzzzzzzzzz", "a-", "-"]) { + show(`decode ${s}`, () => punycode.decode(s)); +} +for (const d of ["mañana.com", "bücher.de", "example.com", "", "a..b", "ex@mple@bücher.de", "user@bücher.de", "日本。jp", "a.b。c", "ÄÖÜ.test", "a.b."]) { + show(`toASCII ${d}`, () => punycode.toASCII(d)); +} +for (const d of ["xn--maana-pta.com", "xn--bcher-kva.de", "XN--bcher-kva.de", "xn--BCHER-KVA.de", "example.com", "user@xn--bcher-kva.de", "xn--.x", "xn--ü.x", "xn--zzzzzzzzzzzzz.x", "xn--a-Kva.x"]) { + show(`toUnicode ${d}`, () => punycode.toUnicode(d)); +} +show("ucs2.decode", () => punycode.ucs2.decode("aé😀\ud800b\udc00")); +show("ucs2.encode", () => punycode.ucs2.encode([97, 0xe9, 0x1f600, 0xd800, 0xdc00, 98])); +show("ucs2.encode join", () => punycode.ucs2.encode([0xd83d, 0xde00]).length); +show("ucs2.encode range", () => punycode.ucs2.encode([0x110000])); +show("ucs2 roundtrip", () => punycode.ucs2.encode(punycode.ucs2.decode("x😀\udfffy")) === "x😀\udfffy"); +process.exitCode = 1; diff --git a/tests/corpus/6124-punycode-require-warnings.cjs b/tests/corpus/6124-punycode-require-warnings.cjs new file mode 100644 index 000000000..c3255e6b7 --- /dev/null +++ b/tests/corpus/6124-punycode-require-warnings.cjs @@ -0,0 +1,32 @@ +// DEP0040 for require("punycode"): reported once, on the next tick after +// the requiring turn, to the stderr report and the 'warning' listeners; +// require("node:punycode") is the same module and does not report again. +// process.noDeprecation drops the report and the listener call. The program +// re-runs itself per case and prints each child's output with process ids +// normalized. +"use strict"; +const { spawnSync } = require("child_process"); + +const mode = process.argv[2]; +if (mode === "direct") { + process.on("warning", (w) => console.log("listener", w.name, w.code, w.message)); + process.nextTick(() => console.log("earlier tick")); + console.log("before require"); + const direct = require("punycode"); + console.log("after require", direct.toASCII("bücher.de")); + const prefixed = require("node:punycode"); + console.log("prefixed", prefixed.toUnicode("xn--bcher-kva.de")); + setTimeout(() => console.log("timer"), 0); +} else if (mode === "silenced") { + process.on("warning", (w) => console.log("listener", w.code)); + process.noDeprecation = true; + const silencedModule = require("punycode"); + console.log("silenced", silencedModule.encode("ü")); +} else { + for (const child of ["direct", "silenced"]) { + const run = spawnSync(process.execPath, [process.argv[1], child], { encoding: "utf8" }); + console.log(`== ${child}: status ${run.status}`); + console.log(run.stdout); + console.log(run.stderr.replace(/\(node:\d+\)/g, "(node:)")); + } +} diff --git a/tests/corpus/6125-constants-module.cjs b/tests/corpus/6125-constants-module.cjs new file mode 100644 index 000000000..d7a547ac5 --- /dev/null +++ b/tests/corpus/6125-constants-module.cjs @@ -0,0 +1,26 @@ +// node:constants (deprecated in documentation only, so no warning): one +// frozen object merging os.constants' dlopen, errno, priority and signals +// groups with fs.constants and crypto.constants, in that order. Members +// read like the owning modules' constants, and a constant the platform +// lacks is absent (graceful-fs probes O_SYMLINK with hasOwnProperty). +const constants = require("constants"); +const nodeConstants = require("node:constants"); +const os = require("os"); + +console.log(typeof constants, Object.isFrozen(constants), constants === nodeConstants); +console.log(constants.O_RDONLY, constants.O_WRONLY, constants.O_RDWR); +console.log(constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC); +console.log(constants.S_IFMT, constants.S_IFREG, constants.S_IFDIR, constants.S_IRWXU); +console.log(constants.F_OK, constants.R_OK, constants.W_OK, constants.X_OK); +console.log(constants.COPYFILE_EXCL, constants.UV_FS_COPYFILE_FICLONE, constants.UV_DIRENT_DIR); +console.log(constants.SIGTERM === os.constants.signals.SIGTERM, constants.SIGINT); +console.log(constants.ENOENT === os.constants.errno.ENOENT, constants.EACCES); +console.log(constants.PRIORITY_HIGH, constants.PRIORITY_LOW, constants.RTLD_LAZY); +console.log(constants.TLS1_2_VERSION, constants.RSA_PKCS1_PADDING, typeof constants.defaultCoreCipherList); +console.log(constants.hasOwnProperty("O_SYMLINK") === ("O_SYMLINK" in constants)); +console.log(constants.hasOwnProperty("O_RDWR"), "SIGKILL" in constants, "UV_UDP_REUSEADDR" in constants); +console.log(constants.hasOwnProperty("NOT_A_CONSTANT"), "dlopen" in constants, "errno" in constants); +const keys = Object.keys(constants); +console.log(keys.indexOf("RTLD_LAZY") < keys.indexOf("E2BIG"), keys.indexOf("E2BIG") < keys.indexOf("PRIORITY_LOW")); +console.log(keys.indexOf("PRIORITY_LOW") < keys.indexOf("SIGHUP"), keys.indexOf("SIGHUP") < keys.indexOf("O_RDONLY")); +console.log(keys.indexOf("O_RDONLY") < keys.indexOf("OPENSSL_VERSION_NUMBER"), keys.includes("POINT_CONVERSION_HYBRID")); diff --git a/tests/corpus/6126-punycode-dependency/main.cjs b/tests/corpus/6126-punycode-dependency/main.cjs new file mode 100644 index 000000000..530503a71 --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/main.cjs @@ -0,0 +1,12 @@ +// A dependency under node_modules that requires node:punycode: Node's +// isInsideNodeModules() rule skips DEP0040 for it, so nothing reaches stderr +// or the 'warning' listeners, and the program's own later require of the +// cached module does not report either. +"use strict"; +process.on("warning", (w) => console.log("listener", w.code)); +const toAscii = require("puny-dep"); +const punycode = require("punycode"); + +console.log(toAscii("mañana.com"), toAscii("example.org")); +console.log(punycode.toUnicode("xn--maana-pta.com")); +setTimeout(() => console.log("timer"), 0); diff --git a/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.d.ts b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.d.ts new file mode 100644 index 000000000..89b712210 --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.d.ts @@ -0,0 +1,2 @@ +declare function toAscii(domain: string): string; +export = toAscii; diff --git a/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.js b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.js new file mode 100644 index 000000000..417c44889 --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.js @@ -0,0 +1,8 @@ +// A dependency requiring node:punycode: Node's isInsideNodeModules() rule +// skips DEP0040 for it. +"use strict"; +const punycode = require("punycode"); + +module.exports = function toAscii(domain) { + return punycode.toASCII(domain); +}; diff --git a/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/package.json b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/package.json new file mode 100644 index 000000000..71edeb25c --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/package.json @@ -0,0 +1,5 @@ +{ + "name": "puny-dep", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/corpus/6150-cjs-inline-require/fallback.js b/tests/corpus/6150-cjs-inline-require/fallback.js new file mode 100644 index 000000000..d89da1a00 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/fallback.js @@ -0,0 +1,6 @@ +const preferred = undefined; +module.exports = + preferred || + function fallback() { + return "fallback"; + }; diff --git a/tests/corpus/6150-cjs-inline-require/inner.js b/tests/corpus/6150-cjs-inline-require/inner.js new file mode 100644 index 000000000..7aa065c1e --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/inner.js @@ -0,0 +1,4 @@ +console.log("inner: evaluated"); +exports.inner = function () { + return "inner"; +}; diff --git a/tests/corpus/6150-cjs-inline-require/late.js b/tests/corpus/6150-cjs-inline-require/late.js new file mode 100644 index 000000000..a28525b26 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/late.js @@ -0,0 +1,2 @@ +console.log("late: evaluated"); +exports.value = "late value"; diff --git a/tests/corpus/6150-cjs-inline-require/lib.js b/tests/corpus/6150-cjs-inline-require/lib.js new file mode 100644 index 000000000..94a5872a4 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/lib.js @@ -0,0 +1,6 @@ +console.log("lib: evaluated"); +exports.f = function (x) { + return x * 10; +}; +exports.n = 7; +exports.counter = { hits: 0 }; diff --git a/tests/corpus/6150-cjs-inline-require/main.js b/tests/corpus/6150-cjs-inline-require/main.js new file mode 100644 index 000000000..0ebc20d36 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/main.js @@ -0,0 +1,55 @@ +// Inline require() of program modules: each call evaluates its module at +// exactly that position (or answers from the cache), and the value is the +// module's exports, read statically. +console.log("main: start"); +const f = require("./lib.js").f; +console.log("main: first inline require done", f(1)); +console.log("main: cached", require("./lib.js").n, require("./lib.js").f(2)); +exports.viaInline = require("./lib.js").f; +console.log("main: exported member", exports.viaInline(3), exports.viaInline === f); + +// Mutation through an inline read is visible to every requirer. +require("./lib.js").counter.hits++; +const lib = require("./lib.js"); +console.log("main: shared counter", lib.counter.hits, require("./lib.js").counter === lib.counter); + +// A whole-export function called directly off the require. +const debug = require("./make-logger.js")("app"); +console.log(debug("ready")); + +// Construction through a whole-export class and through a member class. +const p = new (require("./point.js"))(1, 2); +console.log("point", p.x + p.y); +const q = new (require("./shapes.js").Square)(3); +console.log("square", q.area()); + +// Lazy inline require: the module loads at the first call, once. +function late() { + return require("./late.js").value; +} +console.log("main: before late"); +console.log("late", late(), late()); + +// Member calls off an inline require of a table module. +console.log("table member", require("./table.js").twice(4), require("./table.js").label); + +// Table entries holding inline requires. +const table = { verify: require("./verify.js"), sign: require("./lib.js").f }; +console.log("table", table.verify("tok"), table.sign(4)); + +// Re-exports: module.exports = require(...) and through a binding. +const a = require("./reexport-inline.js"); +const b = require("./reexport-binding.js"); +console.log("reexports", a.inner(), b.inner(), a.inner === b.inner); + +// Single computed values as the whole export. +const T = require("./type-error.js"); +try { + throw new T("bad input"); +} catch (e) { + console.log(e instanceof TypeError, String(e)); +} +const pick = require("./pick.js"); +console.log("pick", pick(5), typeof require("./fallback.js"), require("./fallback.js")()); +const reg = require("./registry.js"); +console.log("registry", reg.size(), reg.add("x"), reg.size(), require("./registry.js") === reg); diff --git a/tests/corpus/6150-cjs-inline-require/make-logger.js b/tests/corpus/6150-cjs-inline-require/make-logger.js new file mode 100644 index 000000000..f698b54a7 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/make-logger.js @@ -0,0 +1,6 @@ +console.log("make-logger: evaluated"); +module.exports = function (ns) { + return function (msg) { + return ns + ": " + msg; + }; +}; diff --git a/tests/corpus/6150-cjs-inline-require/package.json b/tests/corpus/6150-cjs-inline-require/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/corpus/6150-cjs-inline-require/pick.js b/tests/corpus/6150-cjs-inline-require/pick.js new file mode 100644 index 000000000..aadc1eb8e --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/pick.js @@ -0,0 +1,7 @@ +function double(x) { + return x * 2; +} +function triple(x) { + return x * 3; +} +module.exports = process.env.SCRIPTC_CORPUS_NEVER_SET ? triple : double; diff --git a/tests/corpus/6150-cjs-inline-require/point.js b/tests/corpus/6150-cjs-inline-require/point.js new file mode 100644 index 000000000..0f002800c --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/point.js @@ -0,0 +1,6 @@ +module.exports = class Point { + constructor(x, y) { + this.x = x; + this.y = y; + } +}; diff --git a/tests/corpus/6150-cjs-inline-require/reexport-binding.js b/tests/corpus/6150-cjs-inline-require/reexport-binding.js new file mode 100644 index 000000000..e8307cc65 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/reexport-binding.js @@ -0,0 +1,3 @@ +const inner = require("./inner.js"); +console.log("reexport-binding: evaluated"); +module.exports = inner; diff --git a/tests/corpus/6150-cjs-inline-require/reexport-inline.js b/tests/corpus/6150-cjs-inline-require/reexport-inline.js new file mode 100644 index 000000000..26b6d44ea --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/reexport-inline.js @@ -0,0 +1,2 @@ +console.log("reexport-inline: evaluated"); +module.exports = require("./inner.js"); diff --git a/tests/corpus/6150-cjs-inline-require/registry.js b/tests/corpus/6150-cjs-inline-require/registry.js new file mode 100644 index 000000000..e34b1015f --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/registry.js @@ -0,0 +1,13 @@ +class Registry { + constructor() { + this.items = []; + } + add(item) { + this.items.push(item); + return this.items.length; + } + size() { + return this.items.length; + } +} +module.exports = new Registry(); diff --git a/tests/corpus/6150-cjs-inline-require/shapes.js b/tests/corpus/6150-cjs-inline-require/shapes.js new file mode 100644 index 000000000..4dd410384 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/shapes.js @@ -0,0 +1,9 @@ +class Square { + constructor(side) { + this.side = side; + } + area() { + return this.side * this.side; + } +} +exports.Square = Square; diff --git a/tests/corpus/6150-cjs-inline-require/table.js b/tests/corpus/6150-cjs-inline-require/table.js new file mode 100644 index 000000000..ce8f1e364 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/table.js @@ -0,0 +1,4 @@ +function twice(x) { + return x * 2; +} +module.exports = { twice, label: "table" }; diff --git a/tests/corpus/6150-cjs-inline-require/type-error.js b/tests/corpus/6150-cjs-inline-require/type-error.js new file mode 100644 index 000000000..483613b44 --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/type-error.js @@ -0,0 +1 @@ +module.exports = TypeError; diff --git a/tests/corpus/6150-cjs-inline-require/verify.js b/tests/corpus/6150-cjs-inline-require/verify.js new file mode 100644 index 000000000..7adb1f64a --- /dev/null +++ b/tests/corpus/6150-cjs-inline-require/verify.js @@ -0,0 +1,4 @@ +console.log("verify: evaluated"); +module.exports = function verify(token) { + return "verified " + token; +}; diff --git a/tests/corpus/6151-cjs-export-chains/chain.js b/tests/corpus/6151-cjs-export-chains/chain.js new file mode 100644 index 000000000..533e35ba8 --- /dev/null +++ b/tests/corpus/6151-cjs-export-chains/chain.js @@ -0,0 +1,10 @@ +let evaluations = 0; +function make() { + evaluations++; + return function () { + return "shared"; + }; +} +exports.a = exports.b = make(); +module.exports.c = exports.d = module.exports.e = 5; +exports.evaluations = evaluations; diff --git a/tests/corpus/6151-cjs-export-chains/constants.js b/tests/corpus/6151-cjs-export-chains/constants.js new file mode 100644 index 000000000..7a67f9f21 --- /dev/null +++ b/tests/corpus/6151-cjs-export-chains/constants.js @@ -0,0 +1 @@ +exports.VERSION = "1.2.3"; diff --git a/tests/corpus/6151-cjs-export-chains/main.js b/tests/corpus/6151-cjs-export-chains/main.js new file mode 100644 index 000000000..394c5e3d2 --- /dev/null +++ b/tests/corpus/6151-cjs-export-chains/main.js @@ -0,0 +1,9 @@ +// Chained member exports share one value, evaluated once; a fresh +// `exports = module.exports = {}` at the top of a module is a no-op reset. +const chain = require("./chain.js"); +console.log(chain.a(), chain.b(), chain.a === chain.b, chain.evaluations); +console.log(chain.c, chain.d, chain.e); +const re = require("./re.js"); +console.log(re.src.length, re.t.FULL, re.tokens.join(",")); +const sem = require("./sem.js"); +console.log(sem.src.join(" "), sem.re.join(","), sem.count()); diff --git a/tests/corpus/6151-cjs-export-chains/package.json b/tests/corpus/6151-cjs-export-chains/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/corpus/6151-cjs-export-chains/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/corpus/6151-cjs-export-chains/re.js b/tests/corpus/6151-cjs-export-chains/re.js new file mode 100644 index 000000000..8839b5cb4 --- /dev/null +++ b/tests/corpus/6151-cjs-export-chains/re.js @@ -0,0 +1,15 @@ +'use strict' +const constants = require("./constants.js"); +exports = module.exports = {} +const src = []; +const t = {}; +exports.src = src; +exports.t = t; +exports.tokens = []; +function createToken(name, value) { + t[name] = src.length; + src.push(value); + exports.tokens.push(name); +} +createToken("FULL", "^" + constants.VERSION + "$"); +createToken("LOOSE", "v?" + constants.VERSION); diff --git a/tests/corpus/6151-cjs-export-chains/sem.js b/tests/corpus/6151-cjs-export-chains/sem.js new file mode 100644 index 000000000..f12b5b25f --- /dev/null +++ b/tests/corpus/6151-cjs-export-chains/sem.js @@ -0,0 +1,16 @@ +'use strict' +// semver's internal/re.js shape: export members assigned in value position. +exports = module.exports = {} +const re = exports.re = [] +const src = exports.src = [] +const t = exports.t = {} +let R = 0 +const createToken = (name, value) => { + const index = R++ + t[name] = index + src[index] = value + re[index] = value.length +} +exports.count = () => R +createToken('MAIN', 'x.y.z') +createToken('LOOSE', 'v?x') diff --git a/tests/corpus/6152-cjs-tsc-interop/fn.js b/tests/corpus/6152-cjs-tsc-interop/fn.js new file mode 100644 index 000000000..09b227f5b --- /dev/null +++ b/tests/corpus/6152-cjs-tsc-interop/fn.js @@ -0,0 +1,3 @@ +module.exports = function square(x) { + return x * x; +}; diff --git a/tests/corpus/6152-cjs-tsc-interop/main.js b/tests/corpus/6152-cjs-tsc-interop/main.js new file mode 100644 index 000000000..0924dce35 --- /dev/null +++ b/tests/corpus/6152-cjs-tsc-interop/main.js @@ -0,0 +1,17 @@ +"use strict"; +// tsc's CommonJS output: interop helpers over require(), the void 0 +// preamble, and the (0, x.default)() call spelling. +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.run = void 0; +console.log("main: start"); +const plain_1 = __importDefault(require("./plain.js")); +const stamped_1 = __importDefault(require("./stamped.js")); +const fn_1 = __importDefault(require("./fn.js")); +function run() { + return [plain_1.default.greet("a"), (0, stamped_1.default)("b"), stamped_1.helper(), (0, fn_1.default)(2)].join(" "); +} +exports.run = run; +console.log(run(), plain_1.default.count, typeof fn_1.default); diff --git a/tests/corpus/6152-cjs-tsc-interop/package.json b/tests/corpus/6152-cjs-tsc-interop/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/corpus/6152-cjs-tsc-interop/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/corpus/6152-cjs-tsc-interop/plain.js b/tests/corpus/6152-cjs-tsc-interop/plain.js new file mode 100644 index 000000000..60e6732ab --- /dev/null +++ b/tests/corpus/6152-cjs-tsc-interop/plain.js @@ -0,0 +1,5 @@ +console.log("plain: evaluated"); +exports.greet = function (who) { + return "hello " + who; +}; +exports.count = 3; diff --git a/tests/corpus/6152-cjs-tsc-interop/stamped.js b/tests/corpus/6152-cjs-tsc-interop/stamped.js new file mode 100644 index 000000000..9e844af05 --- /dev/null +++ b/tests/corpus/6152-cjs-tsc-interop/stamped.js @@ -0,0 +1,12 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.helper = exports.default = void 0; +console.log("stamped: evaluated"); +function greet(who) { + return "stamped " + who; +} +exports.default = greet; +function helper() { + return "helper"; +} +exports.helper = helper; diff --git a/tests/corpus/6153-cjs-exports-object/lib.js b/tests/corpus/6153-cjs-exports-object/lib.js new file mode 100644 index 000000000..4f69bfca2 --- /dev/null +++ b/tests/corpus/6153-cjs-exports-object/lib.js @@ -0,0 +1,13 @@ +exports.lookup = function (ext) { + return "application/" + ext; +}; +exports.contentType = function (ext) { + return exports.lookup(ext) + "; charset=utf-8"; +}; +exports.describe = function () { + return module.exports.lookup("xml"); +}; +// A later reassignment is what member calls see from then on. +exports.lookup = function (ext) { + return "text/" + ext; +}; diff --git a/tests/corpus/6153-cjs-exports-object/main.js b/tests/corpus/6153-cjs-exports-object/main.js new file mode 100644 index 000000000..39cf61c6c --- /dev/null +++ b/tests/corpus/6153-cjs-exports-object/main.js @@ -0,0 +1,19 @@ +// A module's own exports object, used inside the module: identity +// comparisons (the UMD self-test), typeof and truthiness, and member calls. +const lib = require("./lib.js"); +(function () { + var root = this; + console.log("wrapper", root === module.exports, root === exports, typeof root); +}).call(this); +function check() { + return [exports === module.exports, typeof exports, typeof module.exports, !exports].join(" "); +} +console.log(check(), module.exports ? "truthy" : "falsy"); +console.log(lib.contentType("json"), lib.describe()); +exports.answer = function () { + return 42; +}; +function ask() { + return module.exports.answer() + exports.answer(); +} +console.log(exports.answer(), ask()); diff --git a/tests/corpus/6153-cjs-exports-object/package.json b/tests/corpus/6153-cjs-exports-object/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/corpus/6153-cjs-exports-object/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/corpus/6154-cjs-require-nested-destructure/constants.js b/tests/corpus/6154-cjs-require-nested-destructure/constants.js new file mode 100644 index 000000000..6a53c5d81 --- /dev/null +++ b/tests/corpus/6154-cjs-require-nested-destructure/constants.js @@ -0,0 +1,8 @@ +exports.MAX_LENGTH = 1024; +exports.SEP = "/"; +exports.helper = function (x) { + return x * 3; +}; +exports.bump = function () { + exports.MAX_LENGTH = 2048; +}; diff --git a/tests/corpus/6154-cjs-require-nested-destructure/errors.js b/tests/corpus/6154-cjs-require-nested-destructure/errors.js new file mode 100644 index 000000000..bdddfec3f --- /dev/null +++ b/tests/corpus/6154-cjs-require-nested-destructure/errors.js @@ -0,0 +1,12 @@ +console.log("errors: evaluated"); +const codes = { + ERR_ONE: { code: "ERR_ONE" }, + ERR_TWO: { code: "ERR_TWO" }, +}; +class AbortError extends Error { + constructor() { + super("aborted"); + this.name = "AbortError"; + } +} +module.exports = { codes, AbortError, version: "4.0" }; diff --git a/tests/corpus/6154-cjs-require-nested-destructure/main.js b/tests/corpus/6154-cjs-require-nested-destructure/main.js new file mode 100644 index 000000000..d928d5a27 --- /dev/null +++ b/tests/corpus/6154-cjs-require-nested-destructure/main.js @@ -0,0 +1,14 @@ +// Nested patterns under a named export of a program module destructure +// that export's value at the require (a snapshot), while plain keys stay +// live bindings to the export. +console.log("main: start"); +const { codes: { ERR_ONE, ERR_TWO }, AbortError, version } = require("./errors.js"); +console.log(ERR_ONE.code, ERR_TWO.code, new AbortError().name, version); +const errors = require("./errors.js"); +errors.codes.ERR_ONE = { code: "replaced" }; +console.log(ERR_ONE.code, errors.codes.ERR_ONE.code); +// Destructuring a module BINDING snapshots its exports the same way. +const constants = require("./constants.js"); +const { MAX_LENGTH, SEP: sep, helper } = constants; +constants.bump(); +console.log(MAX_LENGTH, constants.MAX_LENGTH, sep, helper(2)); diff --git a/tests/corpus/6154-cjs-require-nested-destructure/package.json b/tests/corpus/6154-cjs-require-nested-destructure/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/corpus/6154-cjs-require-nested-destructure/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/corpus/6155-cjs-export-table-methods/lib.js b/tests/corpus/6155-cjs-export-table-methods/lib.js new file mode 100644 index 000000000..d8aa56495 --- /dev/null +++ b/tests/corpus/6155-cjs-export-table-methods/lib.js @@ -0,0 +1,20 @@ +console.log("lib: start"); +const WIN = { SEP: "\\" }; +const POSIX = { SEP: "/" }; +function describe() { + return "table"; +} +module.exports = { + scale(x) { + return x * 10; + }, + chars(win32) { + return win32 === true ? WIN.SEP : POSIX.SEP; + }, + get label() { + return describe(); + }, + ...require("./more.js"), + k: (console.log("lib: k entry"), 1), +}; +console.log("lib: end"); diff --git a/tests/corpus/6155-cjs-export-table-methods/main.js b/tests/corpus/6155-cjs-export-table-methods/main.js new file mode 100644 index 000000000..b2ce150d0 --- /dev/null +++ b/tests/corpus/6155-cjs-export-table-methods/main.js @@ -0,0 +1,5 @@ +// Export tables with method entries, accessors and spreads of required +// modules: the spread evaluates its module at its place in the table. +console.log("main: start"); +const m = require("./lib.js"); +console.log(m.scale(2), m.chars(true), m.chars(false), m.label, m.extra(), m.k); diff --git a/tests/corpus/6155-cjs-export-table-methods/more.js b/tests/corpus/6155-cjs-export-table-methods/more.js new file mode 100644 index 000000000..4051ce311 --- /dev/null +++ b/tests/corpus/6155-cjs-export-table-methods/more.js @@ -0,0 +1,4 @@ +console.log("more: evaluated"); +exports.extra = function () { + return "more"; +}; diff --git a/tests/corpus/6155-cjs-export-table-methods/package.json b/tests/corpus/6155-cjs-export-table-methods/package.json new file mode 100644 index 000000000..a3c15a7a6 --- /dev/null +++ b/tests/corpus/6155-cjs-export-table-methods/package.json @@ -0,0 +1 @@ +{ "type": "commonjs" } diff --git a/tests/corpus/6156-cjs-function-members-from-ts/consumer.cjs b/tests/corpus/6156-cjs-function-members-from-ts/consumer.cjs new file mode 100644 index 000000000..12299e189 --- /dev/null +++ b/tests/corpus/6156-cjs-function-members-from-ts/consumer.cjs @@ -0,0 +1,4 @@ +const status = require("./status.cjs"); +exports.describe = function (code) { + return code + " " + status.message[code]; +}; diff --git a/tests/corpus/6156-cjs-function-members-from-ts/main.ts b/tests/corpus/6156-cjs-function-members-from-ts/main.ts new file mode 100644 index 000000000..070d576ff --- /dev/null +++ b/tests/corpus/6156-cjs-function-members-from-ts/main.ts @@ -0,0 +1,9 @@ +// A CommonJS module exporting a function that carries its own members +// (statuses' `status.message = codes; module.exports = status`), read from +// TypeScript through the default import: calls, member reads, member calls. +import status from "./status.cjs"; +import { describe } from "./consumer.cjs"; + +console.log(status(404), status.message[200], status.codes.length, status.isOk(200)); +console.log(status.redirect[301] === true, status.redirect[302]); +console.log(describe(500)); diff --git a/tests/corpus/6156-cjs-function-members-from-ts/package.json b/tests/corpus/6156-cjs-function-members-from-ts/package.json new file mode 100644 index 000000000..5ffd9800b --- /dev/null +++ b/tests/corpus/6156-cjs-function-members-from-ts/package.json @@ -0,0 +1 @@ +{ "type": "module" } diff --git a/tests/corpus/6156-cjs-function-members-from-ts/status.cjs b/tests/corpus/6156-cjs-function-members-from-ts/status.cjs new file mode 100644 index 000000000..7a06d9ab1 --- /dev/null +++ b/tests/corpus/6156-cjs-function-members-from-ts/status.cjs @@ -0,0 +1,10 @@ +function status(code) { + return code + 1; +} +status.message = { 200: "OK", 404: "Not Found", 500: "Internal Server Error" }; +status.codes = [200, 404, 500]; +status.redirect = { 301: true, 302: true }; +status.isOk = function (code) { + return code === 200; +}; +module.exports = status; diff --git a/tests/corpus/6160-number-radix-typed.ts b/tests/corpus/6160-number-radix-typed.ts new file mode 100644 index 000000000..4e11e06b2 --- /dev/null +++ b/tests/corpus/6160-number-radix-typed.ts @@ -0,0 +1,36 @@ +// Number.prototype.toString(radix) with a number-typed radix takes the +// typed entry point: ToIntegerOrInfinity truncates the radix (NaN to 0), +// out-of-range radixes throw the same RangeError, and the argument is +// evaluated once, before formatting. An optional radix keeps the generic +// conversion, where undefined means 10. +const radixes = [2, 2.9, 7, 10, 16, 16.5, 35.99, 36]; +const values = [0, -0, 1, -1, 255, -255.5, 0.1, 1 / 3, 2 ** 53, -(2 ** 60), 1e21, 123.456, NaN, Infinity, -Infinity]; +for (const radix of radixes) { + const row: string[] = []; + for (const value of values) row.push(value.toString(radix)); + console.log(radix, row.join(" ")); +} + +let calls = 0; +function nextRadix(): number { + calls++; + return 8 + calls; +} +console.log((1234).toString(nextRadix()), (1234).toString(nextRadix()), calls); + +for (const bad of [-0, 0.5, 1, 1.99, 36.5, 37, -2, NaN, Infinity, -Infinity]) { + try { + console.log((42).toString(bad)); + } catch (error) { + console.log(String(bad), error instanceof RangeError, (error as Error).message); + } +} + +function maybeRadix(i: number): number | undefined { + return i % 2 === 0 ? undefined : 2 + i; +} +for (let i = 0; i < 4; i++) console.log((255).toString(maybeRadix(i))); + +let total = 0; +for (let i = 0; i < 2000; i++) total += (i * 7919).toString(2 + (i % 35)).length; +console.log(total); diff --git a/tests/corpus/6161-dispose-observer-gates.ts b/tests/corpus/6161-dispose-observer-gates.ts new file mode 100644 index 000000000..aa53c9c85 --- /dev/null +++ b/tests/corpus/6161-dispose-observer-gates.ts @@ -0,0 +1,47 @@ +// A program without weak collections still keeps the undeclared keys of +// checked records, and forgets them when the record is freed: a record +// built later at the same address (the allocator reuses freed blocks of +// its size) serializes only its own fields. Class instances and records +// of shapes that never keep a source free without the dispose hook. +interface Row { + id: number; + name: string; +} +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +function parsedRows(round: number): Row[] { + const text = + `[{"id":${round},"extra":"kept-${round}","name":"a"},` + + `{"name":"b","id":${round + 1}},` + + `{"id":${round + 2},"name":"c","tags":[${round}]}]`; + return JSON.parse(text) as Row[]; +} + +let checksum = 0; +for (let round = 0; round < 3; round++) { + const rows = parsedRows(round); + console.log(JSON.stringify(rows)); + // Drop the parsed rows, then build fresh rows of the same shape. + rows.length = 0; + const fresh: Row[] = []; + for (let i = 0; i < 64; i++) fresh.push({ id: i, name: "n" + i }); + const text = JSON.stringify(fresh); + checksum += text.length; + console.log(text.includes("extra"), text.includes("tags"), Object.keys(fresh[0]!).join(",")); +} +console.log(checksum); + +let sum = 0; +for (let i = 0; i < 10000; i++) { + const p = new Point(i, -i); + const r = { a: p.x, b: [p.y] }; + sum += r.a + r.b[0]!; +} +console.log(sum); diff --git a/tests/corpus/6162-dispose-observer-weak-keys.ts b/tests/corpus/6162-dispose-observer-weak-keys.ts new file mode 100644 index 000000000..f9c5738fa --- /dev/null +++ b/tests/corpus/6162-dispose-observer-weak-keys.ts @@ -0,0 +1,41 @@ +// With a weak collection in the program, every object type's free tells +// the collection to forget the key: objects built later at a freed key's +// address (the allocator reuses blocks of the same size) are not members. +class Node { + id: number; + constructor(id: number) { + this.id = id; + } +} +interface Tag { + label: string; +} + +const seenNodes = new WeakSet(); +const seenTags = new WeakSet(); +const seenArrays = new WeakMap(); +for (let i = 0; i < 64; i++) { + seenNodes.add(new Node(i)); + seenTags.add({ label: "t" + i }); + seenArrays.set([i], "a" + i); +} +let hits = 0; +for (let i = 0; i < 64; i++) { + if (seenNodes.has(new Node(i))) hits++; + if (seenTags.has({ label: "t" + i })) hits++; + if (seenArrays.has([i])) hits++; +} +console.log("stale hits", hits); + +const kept = new Node(1); +const tag: Tag = { label: "kept" }; +seenNodes.add(kept); +seenTags.add(tag); +console.log(seenNodes.has(kept), seenTags.has(tag)); +seenNodes.delete(kept); +console.log(seenNodes.has(kept), seenTags.has(tag)); + +// Checked records with undeclared keys in the same program. +const parsed = JSON.parse('{"label":"x","more":1}') as Tag; +seenTags.add(parsed); +console.log(JSON.stringify(parsed), seenTags.has(parsed)); diff --git a/tests/corpus/6163-dispose-observer-workers.ts b/tests/corpus/6163-dispose-observer-workers.ts new file mode 100644 index 000000000..aa5781752 --- /dev/null +++ b/tests/corpus/6163-dispose-observer-workers.ts @@ -0,0 +1,43 @@ +import { Worker, isMainThread, parentPort } from "node:worker_threads"; + +// Each script thread has its own dispose hook: a worker that keeps checked +// record sources and weak keys serializes and forgets them on its own, +// while the main thread runs the same code without sharing its state. +interface Row { + id: number; + name: string; +} +class Key { + id: number; + constructor(id: number) { + this.id = id; + } +} + +function run(who: string, base: number): string[] { + const out: string[] = []; + const text = `[{"id":${base},"extra":"${who}","name":"a"},{"name":"b","id":${base + 1}}]`; + const rows = JSON.parse(text) as Row[]; + out.push(JSON.stringify(rows)); + rows.length = 0; + const fresh: Row[] = []; + for (let i = 0; i < 32; i++) fresh.push({ id: base + i, name: who }); + out.push(String(JSON.stringify(fresh).includes("extra"))); + const seen = new WeakSet(); + for (let i = 0; i < 32; i++) seen.add(new Key(i)); + let stale = 0; + for (let i = 0; i < 32; i++) if (seen.has(new Key(i))) stale++; + out.push(`${who} stale ${stale}`); + return out; +} + +if (isMainThread) { + for (const line of run("main", 0)) console.log(line); + const worker = new Worker(new URL(import.meta.url)); + worker.on("message", (lines: string[]) => { + for (const line of lines) console.log(line); + }); + worker.on("exit", (code) => console.log("worker exit", code)); +} else { + parentPort!.postMessage(run("worker", 100)); +} diff --git a/tests/corpus/6164-regex-replace-typed-callback.ts b/tests/corpus/6164-regex-replace-typed-callback.ts new file mode 100644 index 000000000..8945235cd --- /dev/null +++ b/tests/corpus/6164-regex-replace-typed-callback.ts @@ -0,0 +1,67 @@ +// String replace/replaceAll with typed function replacers. Callbacks whose +// every parameter receives its declared kind in each match (the match, the +// captures every match sets, the offset, the subject) are called through +// the closure directly; the rest keep the generic path. Both must match +// Node: all matches are collected before the first call, calls run in +// order with the offset in UTF-16 code units, and a throw stops the +// replacement. +const entities: Record = { "&": "&", "<": "<", ">": ">", '"': """ }; +const escapeHtml = (text: string): string => text.replace(/[&<>"]/g, (c) => entities[c] ?? c); +console.log(escapeHtml('a & "c"'), escapeHtml("plain")); +console.log("hello world".replace(/o/, (m) => m.toUpperCase())); +console.log("hello world".replaceAll(/o/g, (m) => `[${m}]`)); +console.log("abc".replace(/x/g, () => "never")); + +console.log("a-b-c".replace(/-/g, (m: string, offset: number) => `${m}${offset}`)); +console.log("ab".replace(/b/, (m: string, offset: number, input: string) => `${m}@${offset}/${input}`)); +console.log("id-12 id-345".replace(/(\d+)/g, (_m: string, digits: string) => String(Number(digits) * 2))); +console.log("k1=v1;k2=v2".replace(/(\w+)=(\w+)/g, (_m: string, k: string, v: string) => `${v}=${k}`)); +console.log("x1y22".replace(/(\d)+/g, (m: string, last: string, at: number) => `<${m}:${last}:${at}>`)); +console.log("a=1".replace(/(\w)=(\d)/, (m: string, k: string, v: string, at: number) => `${m}|${k}|${v}|${at}`)); +console.log("ab ab".replace(/(?a)(b)/g, (m: string, a: string, b: string, at: number, all: string) => `${b}${a}${at}${all.length}`)); + +// Offsets count UTF-16 code units; empty matches advance by code points under /u. +console.log("héllo wörld 😀!".replace(/o/g, (m: string, at: number) => `${m}${at}`)); +console.log("😀a".replace(/(?:)/gu, (m: string, at: number) => `[${at}]`)); +console.log("😀a".replace(/(?:)/g, (m: string, at: number) => `[${at}]`)); + +// Captures that some match leaves unset keep the generic path. +console.log("ab".replace(/(a)|(b)/g, (m: string, a?: string, b?: string) => `${m}:${a}:${b};`)); +console.log("ab".replace(/(x)?b/, (m: string, x?: string) => `${m}:${x}`)); +const dynamicPattern = /(\d)/g; +console.log("1-2".replace(dynamicPattern, (m: string, d: string) => d + d)); + +// The callback runs after every match is collected: it can reuse the regex. +const word = /\w+/g; +console.log("one two three".replace(word, (m: string) => `${m.length}${word.lastIndex}`), word.lastIndex); +const sticky = /a/y; +sticky.lastIndex = 1; +console.log("baaa".replace(sticky, (m: string) => m.toUpperCase()), sticky.lastIndex); + +let calls = 0; +try { + "a1b2c3".replace(/\d/g, (m: string) => { + calls++; + if (m === "2") throw new Error("stop at " + m); + return "#"; + }); +} catch (error) { + console.log((error as Error).message, calls); +} +try { + "a1b2".replace(/(\d)|z/g, (m: string, d?: string) => { + if (d === "2") throw new Error("generic stop at " + d); + return "#" + m; + }); +} catch (error) { + console.log((error as Error).message); +} +try { + "aaa".replaceAll(/a/, (m: string) => m); +} catch (error) { + console.log(error instanceof TypeError, (error as Error).message); +} + +let total = 0; +for (let i = 0; i < 3000; i++) total += escapeHtml(`<${i}> & "${i}"`).length; +console.log(total); diff --git a/tests/corpus/6165-regex-replace-callback-throws.ts b/tests/corpus/6165-regex-replace-callback-throws.ts new file mode 100644 index 000000000..57cb8af18 --- /dev/null +++ b/tests/corpus/6165-regex-replace-callback-throws.ts @@ -0,0 +1,26 @@ +// A replace callback that throws after earlier matches were replaced: the +// partial result is discarded and the exception propagates, with the +// callbacks that ran counted exactly as in Node. +let calls = 0; +function replacer(m: string, digit?: string): string { + calls++; + if (digit === "3") throw new RangeError("stop at " + m); + return "<" + m + ">"; +} +for (const subject of ["a1b2c3d4", "x3", "no digits"]) { + calls = 0; + try { + console.log(subject.replace(/(\d)|z/g, replacer), calls); + } catch (error) { + console.log(error instanceof RangeError, (error as Error).message, calls); + } +} +const parts: string[] = []; +for (let i = 0; i < 50; i++) { + try { + parts.push(("k" + i + "-3").replace(/(\d)/g, replacer)); + } catch { + parts.push("thrown"); + } +} +console.log(parts.filter((p) => p === "thrown").length, calls); diff --git a/tests/corpus/6170-flight-dev-stacks-dead.ts b/tests/corpus/6170-flight-dev-stacks-dead.ts new file mode 100644 index 000000000..bddf70f6a --- /dev/null +++ b/tests/corpus/6170-flight-dev-stacks-dead.ts @@ -0,0 +1,173 @@ +// React Flight's production server as compiled from its sources: the V8 +// structured-stack parser (ReactFlightStackConfigV8) is development-only and +// is referenced only after the production `throw` stubs, so a production +// render never parses a stack. Statements after an abrupt statement never +// run: they may use APIs with no static lowering (Error.prepareStackTrace, +// CallSite) and must not make the program keep stack frames. +// V8's structured stack API, declared here as React's Flow build declares +// it (the program's own types, not Node's). +interface CallSite { + getTypeName(): string | null; + getMethodName(): string | null; + getFunctionName(): string | null; + getScriptNameOrSourceURL(): string | null; + getEvalOrigin(): string | undefined; + getLineNumber(): number | null; + getColumnNumber(): number | null; + isNative(): boolean; + isAsync(): boolean; + isConstructor(): boolean; + isToplevel(): boolean; + isEval(): boolean; + toString(): string; +} +declare global { + interface ErrorConstructor { + prepareStackTrace?: (error: Error, structuredStackTrace: CallSite[]) => string; + } +} +type ReactStackTrace = Array<[string, string, number, number, number, number, boolean]>; + +let framesToSkip: number = 0; +let collectedStackTrace: null | ReactStackTrace = null; +const identifierRegExp = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/; + +function getMethodCallName(callSite: CallSite): string { + const typeName = callSite.getTypeName(); + const methodName = callSite.getMethodName(); + const functionName = callSite.getFunctionName(); + let result = ''; + if (functionName) { + if (typeName && identifierRegExp.test(functionName) && functionName !== typeName) { + result += typeName + '.'; + } + result += functionName; + if (methodName && functionName !== methodName && !functionName.endsWith('.' + methodName) && !functionName.endsWith(' ' + methodName)) { + result += ' [as ' + methodName + ']'; + } + } else { + if (typeName) { + result += typeName + '.'; + } + result += methodName ? methodName : ''; + } + return result; +} + +function collectStackTracePrivate(error: Error, structuredStackTrace: CallSite[]): string { + const result: ReactStackTrace = []; + for (let i = framesToSkip; i < structuredStackTrace.length; i++) { + const callSite = structuredStackTrace[i]; + let name = callSite.getFunctionName() || ''; + if (name.includes('react_stack_bottom_frame')) { + break; + } else if (callSite.isNative()) { + result.push([name, '', 0, 0, 0, 0, callSite.isAsync()]); + } else { + if (callSite.isConstructor()) { + name = 'new ' + name; + } else if (!callSite.isToplevel()) { + name = getMethodCallName(callSite); + } + if (name === '') { + name = ''; + } + let filename = callSite.getScriptNameOrSourceURL() || ''; + if (filename === '') { + filename = ''; + if (callSite.isEval()) { + const origin = callSite.getEvalOrigin(); + if (origin) { + filename = origin.toString() + ', '; + } + } + } + const line = callSite.getLineNumber() || 0; + const col = callSite.getColumnNumber() || 0; + result.push([name, filename, line, col, 0, 0, callSite.isAsync()]); + } + } + collectedStackTrace = result; + return ''; +} + +function collectStackTrace(error: Error, structuredStackTrace: CallSite[]): string { + collectStackTracePrivate(error, structuredStackTrace); + const name = error.name || 'Error'; + const message = error.message || ''; + let stack = name + ': ' + message; + for (let i = 0; i < structuredStackTrace.length; i++) { + stack += '\n at ' + structuredStackTrace[i].toString(); + } + return stack; +} + +function parseStackTrace(error: Error, skipFrames: number): ReactStackTrace { + collectedStackTrace = null; + framesToSkip = skipFrames; + const previousPrepare = Error.prepareStackTrace; + Error.prepareStackTrace = collectStackTrace; + let stack; + try { + stack = String(error.stack); + } finally { + Error.prepareStackTrace = previousPrepare; + } + if (collectedStackTrace !== null) { + const result = collectedStackTrace; + collectedStackTrace = null; + return result; + } + return stack.split('\n').length > 0 ? [] : []; +} + +function renderDebugModel(value: any): any { + if (value !== null && typeof value === 'object' && value._debugStack != null) { + return parseStackTrace(value._debugStack, 1); + } + return value; +} + +function emitOutlinedDebugModelChunk(model: any): string { + throw new Error('emitOutlinedDebugModel should never be called in production mode. This is a bug in React.'); + function replacer(this: any, parentPropertyName: string, value: any): any { + return renderDebugModel(this[parentPropertyName]); + } + const json: string = JSON.stringify(model, replacer); + return json; +} + +function outlineComponentInfo(componentInfo: any): string { + throw new Error('outlineComponentInfo should never be called in production mode. This is a bug in React.'); + const counter = { objectLimit: 10 }; + if (componentInfo.debugStack != null) { + counter.objectLimit += parseStackTrace(componentInfo.debugStack, 1).length; + } + return emitOutlinedDebugModelChunk(componentInfo) + counter.objectLimit; +} + +// The production render: elements become `id:json` rows. +type Element = { type: string; props: { [key: string]: string }; children: Element[] }; +function el(type: string, props: { [key: string]: string }, children: Element[]): Element { + return { type, props, children }; +} +function renderModel(node: Element): string { + const children = node.children.map(renderModel); + return '["$","' + node.type + '",null,' + JSON.stringify(node.props).slice(0, -1) + (children.length > 0 ? (Object.keys(node.props).length > 0 ? ',' : '') + '"children":[' + children.join(',') + ']' : '') + '}]'; +} +const rows: string[] = []; +const items: Element[] = []; +for (let i = 0; i < 3; i++) items.push(el('li', { key: 'k' + i }, [])); +rows.push('0:' + renderModel(el('main', {}, [el('ul', { className: 'list' }, items)]))); +console.log(rows.join('\n')); + +// The production stubs themselves run and throw; their DEV bodies never do. +for (const stub of [() => outlineComponentInfo({ debugStack: new Error('owner') }), () => emitOutlinedDebugModelChunk({})]) { + try { + stub(); + } catch (error) { + console.log((error as Error).message); + } +} + +export {}; diff --git a/tests/corpus/6171-error-stack-capture-sites.ts b/tests/corpus/6171-error-stack-capture-sites.ts new file mode 100644 index 000000000..1a8c8494d --- /dev/null +++ b/tests/corpus/6171-error-stack-capture-sites.ts @@ -0,0 +1,79 @@ +// Stack captures keep the frames of every function active when the Error is +// created: callers of constructors and closures, recursion, and errors a +// runtime operation creates and a local catch takes. Leaf functions and +// accessors that cannot create an Error run in between. Captures record +// frames when the Error is created and format them on the first `.stack` +// read. +let lastCaught: Error | null = null; + +function parseNumber(text: string): number { + try { + return JSON.parse(text) as number; + } catch (error) { + lastCaught = error as Error; + return -1; + } +} + +class Failure { + readonly error: Error; + constructor(reason: string) { + this.error = new Error(reason); + } +} +function buildFailure(reason: string): Failure { + return new Failure(reason); +} + +class Counter { + count: number = 0; + get next(): number { + return ++this.count; + } +} + +function leaf(n: number): number { + return n * 2 + 1; +} +function sumLeaves(n: number): number { + let total = 0; + for (let i = 0; i < n; i++) total += leaf(i); + return total; +} +function viaClosure(make: () => Error): Error { + return make(); +} +function recurse(n: number): Error { + return n === 0 ? new Error('deep') : recurse(n - 1); +} +function hasFrame(error: Error, name: string): boolean { + return error.stack!.split('\n').some((line) => line.includes(' ' + name + ' ') || line.endsWith(' ' + name)); +} + +const counter = new Counter(); +console.log(parseNumber('12'), parseNumber('{'), counter.next, counter.next, sumLeaves(4)); +const caught = lastCaught!; +console.log(caught.name, caught.stack!.startsWith('SyntaxError: '), hasFrame(caught, 'parseNumber')); + +const constructed = buildFailure('in constructor').error; +console.log(constructed.stack!.split('\n')[0], hasFrame(constructed, 'buildFailure')); + +const closure = viaClosure(() => new Error('in closure')); +console.log(closure.stack!.split('\n')[0], hasFrame(closure, 'viaClosure')); + +// Frames are recorded at creation: a later limit and later calls do not +// change them, and the first read fixes the text. +Error.stackTraceLimit = 3; +const deep = recurse(10); +Error.stackTraceLimit = 10; +sumLeaves(100); +const first = deep.stack!; +console.log(first.split('\n').length, hasFrame(deep, 'recurse'), first === deep.stack); +deep.message = 'changed'; +console.log(deep.stack === first); + +// An Error created with the limit at zero has no frames. +Error.stackTraceLimit = 0; +const bare = recurse(2); +Error.stackTraceLimit = 10; +console.log(JSON.stringify(bare.stack)); diff --git a/tests/corpus/6172-unreachable-statements.ts b/tests/corpus/6172-unreachable-statements.ts new file mode 100644 index 000000000..53704f694 --- /dev/null +++ b/tests/corpus/6172-unreachable-statements.ts @@ -0,0 +1,85 @@ +// Statements after return, throw, break or continue never run, while the +// declarations among them keep their hoisting: a later function declaration +// is callable from above, a later `var` reads undefined, and a later +// `let`/`const` stays in its temporal dead zone. +function hoistedCall(): number { + return twice(21); + console.log('never'); + function twice(n: number): number { + return n * 2; + } +} + +function hoistedVar(): string { + const before = typeof late; + return before; + var late: string | undefined = 'assigned'; + console.log(late); +} + +function deadZone(): string { + const read = (): number => counted; + try { + return String(read()); + } catch (error) { + return (error as Error).name + ': ' + (error as Error).message; + } + const counted = 1; +} + +function closureReference(): number { + const call = (): number => helper(); + return call(); + function helper(): number { + return 7; + } +} + +function loops(): string { + const seen: string[] = []; + for (let i = 0; i < 4; i++) { + if (i === 1) { + seen.push('skip'); + continue; + seen.push('never'); + } + if (i === 3) { + seen.push('stop'); + break; + seen.push('never'); + } + seen.push('i' + i); + } + return seen.join(','); +} + +function cases(n: number): string { + switch (n) { + case 0: + return 'zero'; + console.log('never'); + case 1: + n += 10; + case 2: + return 'fell to ' + n; + default: + throw new RangeError('n=' + n); + console.log('never'); + } +} + +function stub(): string { + throw new Error('stub'); + const unused = [1, 2, 3].map((x) => x * 2); + return unused.join(); +} + +console.log(hoistedCall(), hoistedVar(), deadZone(), closureReference(), loops()); +console.log(cases(0), cases(1), cases(2)); +for (const run of [() => cases(5), stub]) { + try { + run(); + } catch (error) { + console.log((error as Error).name, (error as Error).message); + } +} diff --git a/tests/corpus/6180-interface-overload-events.ts b/tests/corpus/6180-interface-overload-events.ts new file mode 100644 index 000000000..d76a118bc --- /dev/null +++ b/tests/corpus/6180-interface-overload-events.ts @@ -0,0 +1,95 @@ +// An interface method with literal-discriminated overloads (the event +// registry shape: on("x", (e: X) => R)) holds ONE function at runtime, as +// JavaScript does. Each call site resolves its overload statically; handlers +// whose types differ per overload cross into the stored function as checked +// function values, and the implementation receives them in its own +// callback type. Events keep their identity across that boundary. +export {}; + +interface SaveEvent { + type: "save"; + path: string; + cancel: boolean; +} +interface LoadEvent { + type: "load"; + path: string; +} +interface Context { + cwd: string; +} +interface Api { + on(event: "save", handler: (e: SaveEvent, ctx: Context) => void): () => void; + on(event: "load", handler: (e: LoadEvent, ctx: Context) => boolean): () => void; + on(event: "tick", handler: (n: number) => void): () => void; + name: string; +} + +let nextId = 0; +const saves: { id: number; h: (e: SaveEvent, ctx: Context) => void }[] = []; +const loads: { id: number; h: (e: LoadEvent, ctx: Context) => boolean }[] = []; +const ticks: { id: number; h: (n: number) => void }[] = []; + +function createApi(name: string): Api { + return { + name, + on(event: "save" | "load" | "tick", handler: any): () => void { + const id = ++nextId; + const count = + event === "save" + ? saves.push({ id, h: handler }) + : event === "load" + ? loads.push({ id, h: handler }) + : ticks.push({ id, h: handler }); + console.log(`${name}: registered ${event} #${id} (${count})`); + return () => { + if (event === "save") saves.splice(saves.findIndex((x) => x.id === id), 1); + else if (event === "load") loads.splice(loads.findIndex((x) => x.id === id), 1); + else ticks.splice(ticks.findIndex((x) => x.id === id), 1); + console.log(`${name}: removed ${event} #${id}`); + }; + }, + }; +} + +// An extension sees only the interface. +function extension(pi: Api): () => void { + pi.on("save", (e, ctx) => { + e.cancel = e.path.endsWith(".lock"); + console.log(`save ${ctx.cwd}/${e.path}`); + }); + pi.on("load", (e) => e.path.length > 3); + return pi.on("tick", (n) => console.log("tick", n * 2, pi.name)); +} + +const api = createApi("main"); +const stopTicks = extension(api); +const ctx: Context = { cwd: "/work" }; +for (const path of ["a.txt", "b.lock"]) { + const ev: SaveEvent = { type: "save", path, cancel: false }; + for (const { h } of saves) h(ev, ctx); + console.log(path, "cancelled:", ev.cancel); +} +for (const { h } of loads) console.log(h({ type: "load", path: "ab" }, ctx), h({ type: "load", path: "abcd" }, ctx)); +for (const { h } of ticks) h(21); +stopTicks(); +console.log("ticks left", ticks.length); + +// A named handler value converts the same way as a lambda. +function onTick(n: number): void { + console.log("named tick", n); +} +const off = api.on("tick", onTick); +for (const { h } of ticks) h(7); +off(); + +// A handler that throws propagates through the boundary unchanged. +api.on("load", (e) => { + if (e.path === "") throw new Error("empty path"); + return true; +}); +try { + for (const { h } of loads) h({ type: "load", path: "" }, ctx); +} catch (err) { + console.log("caught", (err as Error).message); +} diff --git a/tests/corpus/6181-interface-overload-arity.ts b/tests/corpus/6181-interface-overload-arity.ts new file mode 100644 index 000000000..ae4dc5192 --- /dev/null +++ b/tests/corpus/6181-interface-overload-arity.ts @@ -0,0 +1,56 @@ +// Arity overloads on interface and type-literal members: positions some +// overloads omit are optional in the stored function, so one implementation +// with optional parameters serves every overload, and a call omitting a +// trailing argument passes undefined exactly as Node does. +export {}; + +interface Formatter { + label(): string; + label(prefix: string): string; + label(prefix: string, width: number): string; +} +const formatter: Formatter = { + label(prefix?: string, width?: number): string { + const text = prefix ?? "none"; + return width === undefined ? text : text.padEnd(width, ".") + "|"; + }, +}; +console.log(formatter.label()); +console.log(formatter.label("p")); +console.log(formatter.label("q", 6)); + +// Overloads spelled as call signatures of a type-literal member. +type Logger = { + log: { + (message: string): void; + (level: number, message: string): void; + }; + lines: string[]; +}; +const lines: string[] = []; +const logger: Logger = { + lines, + log: (a: string | number, b?: string) => { + lines.push(typeof a === "number" ? `[${a}] ${b}` : a); + }, +}; +logger.log("plain"); +logger.log(2, "leveled"); +console.log(logger.lines.join("; ")); + +// A single-signature method with an optional parameter fills its slot the +// same way. +interface Greeter { + greet(name?: string): string; +} +const greeter: Greeter = { + greet(name?: string): string { + return `hello ${name ?? "stranger"}`; + }, +}; +console.log(greeter.greet(), greeter.greet("ada")); + +// Arguments are evaluated once, left to right, before the call. +let n = 0; +const next = (): string => `#${++n}`; +console.log(formatter.label(next(), 4 + n), n); diff --git a/tests/corpus/6182-interface-overload-types.ts b/tests/corpus/6182-interface-overload-types.ts new file mode 100644 index 000000000..17ce95d17 --- /dev/null +++ b/tests/corpus/6182-interface-overload-types.ts @@ -0,0 +1,61 @@ +// Type overloads with one shared implementation: the stored function takes +// the join of the overload parameter types (a scalar union here) and each +// call converts its result back to the resolved overload's type. Records, +// generic overloads, and implementations typed with unions or `any` share +// the same slot. +export {}; + +interface Converter { + conv(x: number): string; + conv(x: string): number; + conv(x: boolean): boolean; +} +const converter: Converter = { + conv(x: number | string | boolean): any { + return typeof x === "number" ? `n${x}` : typeof x === "string" ? x.length : !x; + }, +}; +const s: string = converter.conv(12); +const len: number = converter.conv("abcd"); +const flipped: boolean = converter.conv(false); +console.log(s.toUpperCase(), len + 1, flipped); + +interface Point { + x: number; + y: number; +} +interface Named { + name: string; +} +interface Factory { + make(kind: "point", a: number, b: number): Point; + make(kind: "named", a: string): Named; +} +const factory: Factory = { + make(kind: string, a: number | string, b?: number): any { + return kind === "point" ? { x: a, y: b } : { name: a }; + }, +}; +const p = factory.make("point", 3, 4); +const named = factory.make("named", "zed"); +console.log(Math.hypot(p.x, p.y), named.name.length, p, named); + +// Generic overloads erase their own type parameters to `unknown`. +interface Store { + get(key: "names", fallback: string[]): string[]; + get(key: "count"): number; + at(items: T[], i: number): T; + at(items: string, i: number): string; +} +const data: Record = { names: ["a", "b"], count: 3 }; +const store: Store = { + get(key: string, fallback?: unknown): any { + return data[key] ?? fallback; + }, + at(items: any, i: number): any { + return items[i]; + }, +}; +console.log(store.get("names", []).join("|"), store.get("count") * 2); +console.log(store.at([10, 20, 30], 1) + 1, store.at("xyz", 2).toUpperCase()); + diff --git a/tests/corpus/6183-interface-overload-flow.ts b/tests/corpus/6183-interface-overload-flow.ts new file mode 100644 index 000000000..d2f3151dc --- /dev/null +++ b/tests/corpus/6183-interface-overload-flow.ts @@ -0,0 +1,89 @@ +// Values of an interface with an overloaded member flow like any record: +// through arrays, factory callbacks, and function results. A class that +// implements the interface dispatches to its own overloaded method. +export {}; + +interface Bus { + emit(kind: "num", value: number): void; + emit(kind: "text", value: string): void; + label: string; +} +type Plugin = (bus: Bus) => void; + +const log: string[] = []; +function makeBus(label: string): Bus { + return { + label, + emit(kind: "num" | "text", value: number | string): void { + log.push(`${label}:${kind}:${typeof value}:${value}`); + }, + }; +} +const plugins: Plugin[] = [ + (bus) => bus.emit("num", 1), + (bus) => { + bus.emit("text", "two"); + bus.emit("num", bus.label.length + 2); + }, +]; +const buses: Bus[] = [makeBus("x"), makeBus("yy")]; +for (const bus of buses) for (const plugin of plugins) plugin(bus); +console.log(log.join("\n")); +console.log(typeof buses[1]!.emit, JSON.stringify(buses[0])); + +interface Picker { + pick(kind: "s"): string; + pick(kind: "n"): number; +} +class Impl implements Picker { + private readonly base: number; + constructor(base: number) { + this.base = base; + } + pick(kind: "s"): string; + pick(kind: "n"): number; + pick(kind: "s" | "n"): string | number { + return kind === "s" ? `str${this.base}` : this.base * 7; + } +} +function use(p: Picker): string { + return `${p.pick("s").length}/${p.pick("n") + 1}`; +} +console.log(use(new Impl(1)), use(new Impl(10))); +const literal: Picker = { + pick(kind: "s" | "n"): any { + return kind === "s" ? "literal" : -1; + }, +}; +console.log(use(literal)); + +// Receivers of every shape resolve the overload the same way: a union of +// records sharing the member, an optional chain, and a call result. +interface Measure { + kind: "len"; + of(x: number): string; + of(x: string): number; +} +interface Sign { + kind: "sign"; + of(x: number): string; + of(x: string): number; +} +const measure: Measure = { + kind: "len", + of(x: number | string): any { + return typeof x === "number" ? `#${x}` : x.length; + }, +}; +const sign: Sign = { + kind: "sign", + of(x: number | string): any { + return typeof x === "number" ? (x < 0 ? "-" : "+") : -x.length; + }, +}; +for (const m of [measure, sign] as (Measure | Sign)[]) console.log(m.kind, m.of(-4).length, m.of("xyz") + 1); +const maybe: Measure | undefined = buses.length > 5 ? undefined : measure; +const none: Measure | undefined = buses.length > 5 ? measure : undefined; +console.log(maybe?.of(5), maybe?.of("abc"), none?.of(1), none?.of("q")); +const pickMeasure = (): Measure => measure; +console.log(pickMeasure().of(7).toUpperCase()); diff --git a/tests/corpus/6230-optional-chain-unchecked-receivers.ts b/tests/corpus/6230-optional-chain-unchecked-receivers.ts new file mode 100644 index 000000000..b80f6134a --- /dev/null +++ b/tests/corpus/6230-optional-chain-unchecked-receivers.ts @@ -0,0 +1,98 @@ +// An optional chain short-circuits EVERY later step when its guarded +// receiver is nullish, even when the checker calls that receiver present: +// an array read past the end or a missing index-signature key is undefined +// at runtime, so `choices[0]?.delta.content` must not read `.content` from +// undefined (the streaming SSE shape). Covers property, element, call, +// non-null-assertion, and nested tails, plus the forms that must still throw. +type Delta = { content?: string; parts: number[] }; +type Choice = { + delta: Delta; + pick(): { c: string }; + maybe?: () => { c: string }; +}; + +const none: Choice[] = []; +const some: Choice[] = [ + { delta: { content: "hi", parts: [7, 8] }, pick: () => ({ c: "picked" }) }, + { delta: { parts: [] }, pick: () => ({ c: "second" }), maybe: () => ({ c: "maybe" }) }, +]; + +// Property tails. +console.log("prop", none[0]?.delta.content, some[0]?.delta.content, some[1]?.delta.content); +console.log("prop ===", none[0]?.delta.content === undefined); +console.log("prop ??", none[0]?.delta.content ?? "none", some[0]?.delta.content ?? "none"); +console.log("prop len", none[0]?.delta.parts.length, some[0]?.delta.parts.length); + +// Element tails, with lazy index side effects on the short-circuit. +let evals = 0; +function at(i: number): number { + evals++; + return i; +} +console.log("elem", none[0]?.delta.parts[at(0)], "evals", evals); +console.log("elem", some[0]?.delta.parts[at(1)], "evals", evals); +console.log("elem ?.[]", none?.[0]?.delta.parts[0], some?.[0]?.delta.parts[0]); + +// Call tails: method call results and optional calls inside the chain. +console.log("call", none[0]?.pick().c, some[0]?.pick().c); +console.log("call ?.()", none[0]?.maybe?.().c, some[0]?.maybe?.().c, some[1]?.maybe?.().c); +let pushed = 0; +function sideEffect(): number { + pushed++; + return 1; +} +none[0]?.delta.parts.push(sideEffect()); +some[1]?.delta.parts.push(sideEffect()); +console.log("call args", pushed, some[1]!.delta.parts.length); + +// A non-null assertion stays inside the chain (`a?.b!.c` is one chain). +console.log("assert", none[0]?.delta!.content, some[0]?.delta!.content); +console.log("assert ===", none[0]?.delta!.parts.length === undefined); + +// Index-signature records: a missing key is undefined. +const byId: { [id: string]: Choice } = { a: some[0]! }; +console.log("index", byId["zz"]?.delta.content, byId["a"]?.delta.content); +console.log("index dot", byId.zz?.delta.parts.length, byId.a?.delta.parts.length); + +// Nested receivers and nested guards. +const grid: Choice[][] = [[], some]; +console.log("nested", grid[0]?.[0]?.delta.content, grid[1]?.[0]?.delta.content); +console.log("nested", grid[5]?.[0].delta.content, grid[1]?.[1]?.delta.content); + +// Optional members holding arrays. +const holder: { list?: Choice[] } = {}; +const full: { list?: Choice[] } = { list: some }; +console.log("member", holder.list?.[0].delta.content, full.list?.[0].delta.content); + +// Strings and conditions. +const words: string[] = []; +const padded = [" ab "]; +console.log("string", words[0]?.trim().length, padded[0]?.trim().length); +console.log("typeof", typeof none[0]?.delta.content, typeof some[0]?.delta.content); +if (none[0]?.delta.content) console.log("unreachable"); +else console.log("cond ok"); +console.log(`template ${none[0]?.delta.content} ${some[0]?.delta.content}`); + +// Typed JSON.parse results: an empty choices array short-circuits too. +type Chunk = { choices: { delta: Delta }[] }; +const streamChunk = JSON.parse('{"choices":[]}') as Chunk; +console.log("json", streamChunk.choices[0]?.delta.content ?? "(no delta)"); +const textChunk = JSON.parse('{"choices":[{"delta":{"content":"tok","parts":[]}}]}') as Chunk; +console.log("json", textChunk.choices[0]?.delta.content ?? "(no delta)"); + +// A PRESENT receiver followed by a missing step still throws: the guard +// covers only the receiver it tests. +function message(f: () => unknown): string { + try { + f(); + return "no throw"; + } catch (e) { + return (e as Error).message; + } +} +console.log("throws", message(() => some?.[5].delta)); +console.log("throws", message(() => none[0].delta)); +// Parentheses end the chain: the outer read sees undefined. +const loose = JSON.parse("{}"); +console.log("parens", message(() => (loose.missing?.deep).deeper)); +console.log("no parens", message(() => loose.missing?.deep.deeper)); diff --git a/tests/corpus/error-js-accessor-name-message.js b/tests/corpus/error-js-accessor-name-message.js new file mode 100644 index 000000000..77b8668fa --- /dev/null +++ b/tests/corpus/error-js-accessor-name-message.js @@ -0,0 +1,45 @@ +// A JavaScript Error subclass that overrides name and message with accessor +// pairs, as quickjs-wasi's JSException does. Writing `.name` or `.message` +// on an ordinary Error builds the Error property dispatchers, whose branch +// for this class calls the JavaScript setters with an untyped value. +class GuestException extends Error { + #name; + #message; + constructor(name, message) { + super(); + this.#name = name; + this.#message = message; + } + get name() { + return this.#name; + } + set name(v) { + this.#name = v; + } + get message() { + return this.#message; + } + set message(v) { + this.#message = `[${v}]`; + } +} + +const guest = new GuestException("GuestError", "boom"); +console.log(guest.name, guest.message, String(guest)); +guest.name = "Renamed"; +guest.message = "changed"; +console.log(guest.name, guest.message, String(guest)); + +const plain = new Error("plain"); +plain.name = "PlainName"; +plain.message = "plain message"; +console.log(plain.name, plain.message, String(plain)); + +/** @type {Error[]} */ +const errors = [plain, guest, new TypeError("bad")]; +for (const err of errors) console.log(String(err)); +try { + throw guest; +} catch (caught) { + console.log(caught instanceof GuestException, caught instanceof Error, String(caught)); +} diff --git a/tests/corpus/js-builder-string-method-names/builder.js b/tests/corpus/js-builder-string-method-names/builder.js new file mode 100644 index 000000000..36824c317 --- /dev/null +++ b/tests/corpus/js-builder-string-method-names/builder.js @@ -0,0 +1,22 @@ +// Prototype getters named like String.prototype's HTML methods (bold, +// italics, link, big): each read builds a callable that wraps its input. +const wrap = (tag) => ({ + get() { + const parent = this; + const builder = (text) => builder.prefix(text); + builder.prefix = (text) => parent.prefix(`<${tag}>` + text + ``); + Object.setPrototypeOf(builder, proto); + return builder; + }, +}); +const proto = Object.defineProperties(() => {}, { + bold: wrap("b"), + italics: wrap("i"), + link: wrap("a"), + big: wrap("big"), + red: wrap("red"), +}); +const root = (text) => text; +root.prefix = (text) => text; +Object.setPrototypeOf(root, proto); +export default root; diff --git a/tests/corpus/js-builder-string-method-names/main.ts b/tests/corpus/js-builder-string-method-names/main.ts new file mode 100644 index 000000000..5cab8e4ca --- /dev/null +++ b/tests/corpus/js-builder-string-method-names/main.ts @@ -0,0 +1,14 @@ +// A TypeScript caller reaching JavaScript builders whose member names +// collide with String.prototype methods: the receiver is a function, so +// each call reads the function's inherited getter, not the string method. +import builder from "./builder.js"; + +// TypeScript infers no members for the builder's prototype getters. +const tags: any = builder; + +console.log(tags.bold("strong")); +console.log(tags.red.bold("nested")); +console.log(tags.italics.link("deep").length); +console.log(tags.big.red.italics("three")); +const chain = tags.link; +console.log(chain.bold("from a binding")); diff --git a/tests/corpus/js-callable-builder.js b/tests/corpus/js-callable-builder.js new file mode 100644 index 000000000..3697d788d --- /dev/null +++ b/tests/corpus/js-callable-builder.js @@ -0,0 +1,110 @@ +// A chalk-style builder: every style read through a function's runtime +// prototype chain runs a getter that creates a new callable builder, +// links it to a shared prototype with Object.setPrototypeOf, and caches +// it on the receiver with Object.defineProperty. +const GENERATOR = Symbol("GENERATOR"); +const STYLER = Symbol("STYLER"); +const IS_EMPTY = Symbol("IS_EMPTY"); +const codes = { red: [31, 39], green: [32, 39], bold: [1, 22], underline: [4, 24] }; +let getterRuns = 0; + +const styles = Object.create(null); +for (const [name, [open, close]] of Object.entries(codes)) { + styles[name] = { + get() { + getterRuns++; + const builder = createBuilder(this, createStyler(`<${open}>`, ``, this[STYLER]), this[IS_EMPTY]); + Object.defineProperty(this, name, { value: builder }); + return builder; + }, + }; +} +styles.visible = { + get() { + const builder = createBuilder(this, this[STYLER], true); + Object.defineProperty(this, "visible", { value: builder }); + return builder; + }, +}; + +const proto = Object.defineProperties(() => {}, { + ...styles, + level: { + enumerable: true, + get() { + return this[GENERATOR].level; + }, + set(level) { + this[GENERATOR].level = level; + }, + }, +}); + +function createStyler(open, close, parent) { + if (parent === undefined) return { open, close, openAll: open, closeAll: close, parent }; + return { open, close, openAll: parent.openAll + open, closeAll: close + parent.closeAll, parent }; +} + +function createBuilder(self, styler, isEmpty) { + const builder = (...args) => applyStyle(builder, args.length === 1 ? "" + args[0] : args.join(" ")); + Object.setPrototypeOf(builder, proto); + builder[GENERATOR] = self; + builder[STYLER] = styler; + builder[IS_EMPTY] = isEmpty; + return builder; +} + +function applyStyle(self, text) { + if (self.level <= 0 || !text) return self[IS_EMPTY] ? "" : text; + let styler = self[STYLER]; + if (styler === undefined) return text; + const { openAll, closeAll } = styler; + if (text.includes(" strings.join(" "); + chalk.level = options && options.level !== undefined ? options.level : 1; + Object.setPrototypeOf(chalk, createChalk.prototype); + return chalk; +} +Object.setPrototypeOf(createChalk.prototype, Function.prototype); +Object.defineProperties(createChalk.prototype, styles); + +const chalk = createChalk(); +console.log(chalk.red("x")); +console.log(chalk.red.bold("nested")); +console.log(chalk.green("a " + chalk.red("b") + " c")); +console.log(chalk.underline.green.bold("three", "args", 3)); +console.log(JSON.stringify(chalk.red("")), chalk.visible("v"), chalk.red.visible("rv")); +console.log(chalk("plain", "call"), typeof chalk, typeof chalk.red, typeof chalk.red.bold); +console.log(chalk.red === chalk.red, chalk.red.bold === chalk.red.bold, getterRuns); +console.log(Object.getPrototypeOf(chalk) === createChalk.prototype); +console.log(Object.getPrototypeOf(chalk.red) === proto, Object.getPrototypeOf(chalk.red.bold) === proto); +console.log(Object.getPrototypeOf(createChalk.prototype) === Function.prototype); +console.log(chalk.apply(null, ["via", "apply"])); +console.log(Object.keys(chalk).join(","), Object.hasOwn(chalk, "red"), "red" in chalk); + +const red = chalk.red; +chalk.level = 0; +console.log(red("off"), red.level, chalk.level, JSON.stringify(red.visible("hidden"))); +red.level = 2; +console.log(chalk.level, chalk.green.level, red("on")); +const quiet = createChalk({ level: 0 }); +console.log(quiet.red.bold("quiet"), quiet.level, chalk.level); diff --git a/tests/corpus/js-class-constructor-returns/factory.js b/tests/corpus/js-class-constructor-returns/factory.js new file mode 100644 index 000000000..9fe0b84c4 --- /dev/null +++ b/tests/corpus/js-class-constructor-returns/factory.js @@ -0,0 +1,35 @@ +// Classes whose constructor returns a value: `new` answers that value. +const make = (options = {}) => { + const fn = (...parts) => parts.join(options.separator ?? " "); + fn.level = options.level ?? 1; + return fn; +}; + +export class Joiner { + constructor(options) { + return make(options); + } +} + +export class Record { + constructor(name, size) { + const value = { name, size, kind: "record" }; + if (size > 10) return { ...value, large: true }; + return value; + } +} + +let constructed = 0; +export class Counted { + constructor() { + constructed++; + return { id: constructed }; + } +} + +const j = new Joiner({ separator: "-", level: 3 }); +console.log(typeof j, j("a", "b", "c"), j.level); +const plain = new Joiner(); +console.log(plain("x", "y"), plain.level); +console.log(JSON.stringify(new Record("box", 4)), JSON.stringify(new Record("crate", 40))); +console.log(new Counted().id, new Counted().id, constructed); diff --git a/tests/corpus/js-class-constructor-returns/main.ts b/tests/corpus/js-class-constructor-returns/main.ts new file mode 100644 index 000000000..800016329 --- /dev/null +++ b/tests/corpus/js-class-constructor-returns/main.ts @@ -0,0 +1,11 @@ +// The same classes constructed from TypeScript: the values come from the +// constructors' returns. +import { Counted, Joiner, Record } from "./factory.js"; + +// TypeScript types `new` by the class; the values are what the constructors return. +const joiner: any = new Joiner({ separator: "+" }); +console.log(joiner("1", "2")); +const record = new Record("tube", 11); +console.log(JSON.stringify(record)); +const counted: any = new Counted(); +console.log(counted.id); diff --git a/tests/corpus/js-function-prototype-value.js b/tests/corpus/js-function-prototype-value.js new file mode 100644 index 000000000..3efe4540e --- /dev/null +++ b/tests/corpus/js-function-prototype-value.js @@ -0,0 +1,17 @@ +// Function.prototype as a value: a callable answering undefined, with an +// empty name and length 0, usable as a prototype-chain link. +const fp = Function.prototype; +console.log(typeof fp, fp === Function.prototype, JSON.stringify(fp.name), fp.length); +console.log(fp(), fp(1, 2, 3)); + +function Base() {} +Object.setPrototypeOf(Base.prototype, Function.prototype); +console.log(Object.getPrototypeOf(Base.prototype) === Function.prototype); + +const callable = (...args) => args.length; +Object.setPrototypeOf(callable, Base.prototype); +console.log(callable(1, 2), Object.getPrototypeOf(callable) === Base.prototype); +console.log(callable.apply(null, [1, 2, 3]), callable.call(null, 1)); + +const plain = Object.setPrototypeOf({ own: 1 }, Function.prototype); +console.log(typeof plain, plain.own, Object.getPrototypeOf(plain) === Function.prototype); diff --git a/tests/corpus/js-number-parse-dynamic.js b/tests/corpus/js-number-parse-dynamic.js new file mode 100644 index 000000000..e4b050046 --- /dev/null +++ b/tests/corpus/js-number-parse-dynamic.js @@ -0,0 +1,8 @@ +// Number.parseInt / Number.parseFloat over checked-dynamic values apply +// the parsers' own ToString, as supports-color does with FORCE_COLOR. +const env = JSON.parse('{"FORCE_COLOR":"2","DEPTH":" 3.5px","EMPTY":"","HEX":"ff"}'); +console.log(Number.parseInt(env.FORCE_COLOR, 10), Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3)); +console.log(Number.parseInt(env.MISSING, 10), Number.parseInt(env.EMPTY, 10), Number.parseInt(env.HEX, 16)); +console.log(Number.parseFloat(env.DEPTH), Number.parseFloat(env.MISSING)); +const values = JSON.parse("[12, true, null]"); +console.log(Number.parseInt(values[0], 10), Number.parseInt(values[1], 36), Number.parseInt(values[2], 36)); diff --git a/tests/corpus/js-process-destructure.mjs b/tests/corpus/js-process-destructure.mjs new file mode 100644 index 000000000..81660e22c --- /dev/null +++ b/tests/corpus/js-process-destructure.mjs @@ -0,0 +1,12 @@ +// Data members of the process object taken off by destructuring. +import process from "node:process"; + +const { env, argv, platform } = process; +console.log(env.SCRIPTC_TEST_ENV, "SCRIPTC_TEST_ENV" in env, "SCRIPTC_NOT_SET_ANYWHERE" in env); +console.log(Array.isArray(argv), typeof argv[0], platform === process.platform); + +function readFlags() { + const { env: environment, pid } = process; + return [typeof environment.SCRIPTC_TEST_ENV, pid === process.pid]; +} +console.log(readFlags().join(" ")); diff --git a/tests/corpus/js-reshaped-object-literal.js b/tests/corpus/js-reshaped-object-literal.js new file mode 100644 index 000000000..c184cc388 --- /dev/null +++ b/tests/corpus/js-reshaped-object-literal.js @@ -0,0 +1,42 @@ +// An object literal the program reshapes at run time (runtime-keyed +// writes, defineProperty, delete): every read sees the live object, not +// the layout TypeScript inferred for the literal (ansi-styles' +// assembleStyles shape). +const styles = { + modifier: { reset: [0, 0], bold: [1, 22] }, + color: { red: [31, 39], green: [32, 39] }, +}; + +function assemble() { + const codes = new Map(); + for (const [groupName, group] of Object.entries(styles)) { + for (const [styleName, style] of Object.entries(group)) { + styles[styleName] = { open: `<${style[0]}>`, close: `` }; + group[styleName] = styles[styleName]; + codes.set(style[0], style[1]); + } + Object.defineProperty(styles, groupName, { value: group, enumerable: false }); + } + Object.defineProperty(styles, "codes", { value: codes, enumerable: false }); + styles.color.close = ""; + styles.color.ansi = (code) => `[${code}]`; + Object.defineProperties(styles, { + double: { value: (n) => n * 2, enumerable: false }, + }); + return styles; +} + +const ansi = assemble(); +console.log(Object.keys(ansi).join(",")); +console.log(ansi.red.open, ansi.color.red.close, ansi.color.close, ansi.color.ansi(5)); +console.log(ansi.codes.get(31), ansi.codes.size, ansi.double(21)); +console.log(ansi.modifier.bold === ansi.bold, Object.keys(ansi.color).join(",")); +for (const [name, style] of Object.entries(ansi)) console.log(name, style.open, style.close); + +const table = { a: 1, b: 2, c: 3 }; +const drop = (key) => { + delete table[key]; +}; +drop("b"); +table["d" + 1] = 4; +console.log(JSON.stringify(table), Object.keys(table).length, "b" in table); diff --git a/tests/corpus/scratch-string-slices.ts b/tests/corpus/scratch-string-slices.ts new file mode 100644 index 000000000..487883d6e --- /dev/null +++ b/tests/corpus/scratch-string-slices.ts @@ -0,0 +1,116 @@ +// Slices read only by comparisons, numeric parsers, collection probes, +// switch discriminants and lent helper parameters, including the keys a +// map or set keeps after the slice that produced them is gone. +const ascii = "GET /api/users?id=42 200 17.5ms"; +const mixed = "Aé水x😀yz-12.5e1 ok"; +const bounds = [-Infinity, -40, -8, -2.5, -1, -0.5, -0, 0, 0.5, 1, 2, 3, 4.9, 5, 7, 10, 40, Infinity, NaN]; + +function compare(source: string, start: number, end: number): string { + const s = source.slice(start, end); + const t = source.substring(start, end); + const parts = [ + String(s === "GET"), + String(t === "GET"), + String(source.slice(start, end) === source.substring(start, end)), + String(source.slice(start, end) < "M"), + String(source.substring(start, end) >= "a"), + String(s.length), + String(t.length), + String(s.charCodeAt(0)), + String(t.charCodeAt(t.length - 1)), + String(s.indexOf("a")), + String(t.includes("😀")), + String(s.startsWith("A")), + String(t.endsWith("z")), + ]; + return parts.join(","); +} +for (const source of [ascii, mixed]) { + for (const start of bounds) { + const row: string[] = []; + for (const end of bounds) row.push(compare(source, start, end)); + console.log(row.join(" | ")); + } +} + +// Numeric parsing of slices. +const numbers = " 42|-0|0x1F|1e3|.5|Infinity|-Infinity|12px|abc|+7.25| 9 |0b11|1_000|"; +let offset = 0; +while (offset < numbers.length) { + const bar = numbers.indexOf("|", offset); + const field = numbers.slice(offset, bar); + console.log( + JSON.stringify(field), + Number(field), + +numbers.substring(offset, bar), + parseInt(field), + parseInt(numbers.slice(offset, bar), 16), + parseFloat(field), + Object.is(Number(numbers.slice(offset, bar)), -0), + ); + offset = bar + 1; +} +console.log(Number(mixed.slice(9, 16)), parseFloat(mixed.slice(9)), parseInt(mixed.slice(10, 12))); + +// Collection probes and insertions keyed by slices. The stored keys must +// outlive every later slice written to the same site. +const counts = new Map(); +const seen = new Set(); +const words = "alpha beta gamma alpha delta beta alpha epsilon-with-a-much-longer-key-than-the-stack-buffer-holds-alpha zeta é-key é-key x x"; +for (let round = 0; round < 3; round++) { + let start = 0; + while (start < words.length) { + let end = words.indexOf(" ", start); + if (end < 0) end = words.length; + const word = words.slice(start, end); + counts.set(word, (counts.get(word) ?? 0) + 1); + if (!seen.has(word)) seen.add(word); + if (counts.get(words.substring(start, end)) === 2) seen.delete(word); + start = end + 1; + } +} +console.log([...counts.entries()].map(([key, value]) => `${key}=${value}`).join(",")); +console.log([...seen].join(",")); +console.log(counts.has(ascii.slice(0, 3)), counts.has(words.slice(0, 5)), seen.has(words.slice(6, 10))); +const index = new Map(); +for (let i = 0; i + 3 <= ascii.length; i++) index.set(ascii.slice(i, i + 3), i); +console.log(index.size, index.get("api"), index.get(ascii.substring(5, 8)), [...index.keys()].slice(0, 6).join("/")); +const reads = new Map([["api", "first"], ["users", "second"]]); +console.log(reads.get(ascii.slice(5, 8)), reads.get(ascii.slice(9, 14)) ?? "none", reads.get(ascii.slice(0, 1)) ?? "none"); + +// Switch discriminants. +function method(line: string): number { + switch (line.slice(0, line.indexOf(" "))) { + case "GET": + return 1; + case "POST": + return 2; + case "é": + return 3; + default: + return 0; + } +} +console.log(method(ascii), method("POST /x"), method("é y"), method("PATCH /x"), method("nospace")); + +// Lent helper parameters, including recursion through a lent parameter. +function isKeyword(text: string): boolean { + return text === "let" || text === "const" || text === "return"; +} +function depth(text: string, n: number): number { + if (n === 0) return text.length; + return depth(text, n - 1) + (text === "abc" ? 1 : 0); +} +function keep(text: string): string[] { + return [text]; +} +const program = "let x = 1; const y = 2; return x + y; letter"; +let keywords = 0; +const kept: string[][] = []; +for (let i = 0; i < program.length; i++) { + for (let j = i + 2; j <= Math.min(program.length, i + 6); j++) { + if (isKeyword(program.slice(i, j))) keywords++; + } + if (i % 9 === 0) kept.push(keep(program.slice(i, i + 4))); +} +console.log(keywords, depth("abcdef".slice(0, 3), 4), kept.map((k) => k[0]).join("|")); diff --git a/tests/corpus/truthy-object-logical.ts b/tests/corpus/truthy-object-logical.ts new file mode 100644 index 000000000..0b84b760b --- /dev/null +++ b/tests/corpus/truthy-object-logical.ts @@ -0,0 +1,31 @@ +// `&&` and `||` whose left operand is an object, array or function value: +// every object is truthy, so `o && x` evaluates `o` and answers `x`, and +// `o || x` answers `o` without evaluating `x`. +interface Stream { + isTTY: boolean; + columns: number; +} +let reads = 0; +const stream = (): Stream => { + reads++; + return { isTTY: reads % 2 === 1, columns: 80 }; +}; +function tty(s: Stream): boolean { + return s && s.isTTY; +} +console.log(tty({ isTTY: true, columns: 1 }), tty({ isTTY: false, columns: 2 })); +const width = stream() && stream().columns; +console.log(width, reads); +const list = [3, 1, 2]; +const first = list && list.length; +console.log(first); +const fn = (n: number): number => n * 2; +const doubled = fn && fn(21); +console.log(doubled); +let evaluated = false; +const fallback = (): Stream => { + evaluated = true; + return { isTTY: false, columns: 0 }; +}; +const chosen = stream() || fallback(); +console.log(chosen.columns, evaluated, reads); diff --git a/tests/corpus/webassembly-feature-probe.js b/tests/corpus/webassembly-feature-probe.js new file mode 100644 index 000000000..7a164fd90 --- /dev/null +++ b/tests/corpus/webassembly-feature-probe.js @@ -0,0 +1,16 @@ +// @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). +function supportsWasm() { + return typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function"; +} + +async function instantiate(bytes, imports) { + const { instance } = await WebAssembly.instantiate(bytes, imports); + return instance.exports; +} + +const backend = process.argv.includes("--wasm") ? instantiate : null; +console.log("supports wasm:", supportsWasm()); +console.log("backend:", backend === null ? "javascript" : "wasm"); diff --git a/tests/corpus/webassembly-type-mentions.ts b/tests/corpus/webassembly-type-mentions.ts new file mode 100644 index 000000000..fbaec710e --- /dev/null +++ b/tests/corpus/webassembly-type-mentions.ts @@ -0,0 +1,22 @@ +// Declarations that mention the WebAssembly API's types compile: the types +// have the checked-dynamic representation, and only a reached use of the +// API itself is refused. typeof needs no value and folds to Node's answer. +interface LoaderOptions { + wasm?: BufferSource | WebAssembly.Module; + imports?: WebAssembly.Imports; + memory?: WebAssembly.Memory; +} + +function describe(name: string, options: LoaderOptions): string { + const parts = [name]; + parts.push(options.wasm === undefined ? "no module" : "module given"); + parts.push(options.imports === undefined ? "no imports" : "imports given"); + return parts.join(", "); +} + +console.log(describe("empty", {})); +console.log(describe("bytes", { wasm: new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]) })); +console.log(typeof WebAssembly, typeof globalThis.WebAssembly); +console.log(typeof WebAssembly.compile, typeof WebAssembly.instantiate, typeof WebAssembly.validate); +console.log(typeof WebAssembly.Module, typeof WebAssembly.Instance, typeof WebAssembly.Memory); +console.log(typeof WebAssembly.RuntimeError, typeof WebAssembly.CompileError); diff --git a/tests/coverage-fixtures/tier-demotions.ts b/tests/coverage-fixtures/tier-demotions.ts new file mode 100644 index 000000000..52f2058f1 --- /dev/null +++ b/tests/coverage-fixtures/tier-demotions.ts @@ -0,0 +1,25 @@ +// The report-only tier assignment of `scriptc coverage --explain-dynamic`. +class Point { + constructor( + public x: number, + public y: number, + ) {} +} + +// Reflection on a class: the dynamic tier would give Point a prototype object. +(Point.prototype as any).norm = function () { + return 0; +}; + +// Already dynamic: a property read on an `any` value. +function readName(text: string): string { + const data: any = JSON.parse(text); + return String(data.name); +} + +// An operation without a static lowering, on a parameter. +function precise(n: number): string { + return n.toPrecision(3); +} + +console.log(readName('{"name":"a"}'), precise(1.5), new Point(1, 2).x); diff --git a/tests/coverage-fixtures/wasm/add.wasm b/tests/coverage-fixtures/wasm/add.wasm new file mode 100644 index 000000000..357f72da7 Binary files /dev/null and b/tests/coverage-fixtures/wasm/add.wasm differ diff --git a/tests/coverage-fixtures/wasm/build/generated.wasm b/tests/coverage-fixtures/wasm/build/generated.wasm new file mode 100644 index 000000000..3697e05bc Binary files /dev/null and b/tests/coverage-fixtures/wasm/build/generated.wasm differ diff --git a/tests/coverage-fixtures/wasm/glue.js b/tests/coverage-fixtures/wasm/glue.js new file mode 100644 index 000000000..631560632 --- /dev/null +++ b/tests/coverage-fixtures/wasm/glue.js @@ -0,0 +1,18 @@ +import { existsSync } from "node:fs"; + +// (module (func (export "answer") (result i32) i32.const 42)), inlined the +// way undici inlines llhttp. +const ANSWER = "AGFzbQEAAAABBQFgAAF/AwIBAAcKAQZhbnN3ZXIAAAoGAQQAQSoL"; + +export function hasModule() { + return existsSync(new URL("./add.wasm", import.meta.url).pathname); +} + +export function inlineModule() { + return ANSWER; +} + +export async function instantiate(bytes) { + const { instance } = await WebAssembly.instantiate(bytes, {}); + return instance.exports; +} diff --git a/tests/coverage-fixtures/wasm/main.ts b/tests/coverage-fixtures/wasm/main.ts new file mode 100644 index 000000000..a2d80b6b3 --- /dev/null +++ b/tests/coverage-fixtures/wasm/main.ts @@ -0,0 +1,9 @@ +// 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"; +import { describeModule } from "wasm-fixture-pkg"; + +console.log(hasModule(), inlineModule().length, describeModule()); diff --git a/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/index.d.ts b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/index.d.ts new file mode 100644 index 000000000..2de8deed5 --- /dev/null +++ b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/index.d.ts @@ -0,0 +1 @@ +export declare function describeModule(path?: string): string; diff --git a/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/index.js b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/index.js new file mode 100644 index 000000000..ecb29cd32 --- /dev/null +++ b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/index.js @@ -0,0 +1,6 @@ +// Loads lib/core.wasm from a path computed at run time, so only the +// package scan finds the module. +export function describeModule(path) { + const where = path ?? "lib/core.wasm"; + return typeof WebAssembly === "object" ? `wasm at ${where}` : "no wasm"; +} diff --git a/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm new file mode 100644 index 000000000..dd711f095 Binary files /dev/null and b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm differ diff --git a/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/package.json b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/package.json new file mode 100644 index 000000000..af753b539 --- /dev/null +++ b/tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/package.json @@ -0,0 +1,7 @@ +{ + "name": "wasm-fixture-pkg", + "version": "1.0.0", + "type": "module", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/coverage-fixtures/wasm/package.json b/tests/coverage-fixtures/wasm/package.json new file mode 100644 index 000000000..a68fcd213 --- /dev/null +++ b/tests/coverage-fixtures/wasm/package.json @@ -0,0 +1,8 @@ +{ + "name": "wasm-coverage-fixture", + "private": true, + "type": "module", + "scriptc": { + "wasm": ["build/generated.wasm"] + } +} diff --git a/tests/diagnostics/builtin-class-alias-escape.ts b/tests/diagnostics/builtin-class-alias-escape.ts new file mode 100644 index 000000000..b3f9be5d3 --- /dev/null +++ b/tests/diagnostics/builtin-class-alias-escape.ts @@ -0,0 +1,15 @@ +// A builtin class alias owns no runtime value in a TypeScript file: +// construction, instanceof, typeof and .name compile, other uses report +// at the use. A let binding of a class is not an alias. +import { EventEmitter } from "node:events"; + +const Emitter = EventEmitter; +const kinds = [Emitter]; +console.log(kinds.length); + +const M = Map; +const boxed: unknown = M; +console.log(boxed !== undefined); + +let Mutable = EventEmitter; +console.log(typeof Mutable); diff --git a/tests/diagnostics/class-values.ts b/tests/diagnostics/class-values.ts index 02c5360fa..3a53e5185 100644 --- a/tests/diagnostics/class-values.ts +++ b/tests/diagnostics/class-values.ts @@ -18,8 +18,10 @@ class Unrelated { static tag = "unrelated"; } -// Builtin classes as values: their construction is libCall-shaped. +// Builtin classes as values: a const alias resolves at compile time, and +// an array of builtin classes has no element type. const E = Error; +const errorClasses = [E]; // Classes extending builtin bases as values (the error-message completion // rule has no thunk ABI). diff --git a/tests/diagnostics/collection-iterator-helpers.ts b/tests/diagnostics/collection-iterator-helpers.ts index 9d264ec51..127b5c64b 100644 --- a/tests/diagnostics/collection-iterator-helpers.ts +++ b/tests/diagnostics/collection-iterator-helpers.ts @@ -1,10 +1,9 @@ -// Stored collection cursors support next/iteration, but helper methods are -// refused explicitly instead of dispatching to array helpers at runtime. -const map = new Map(); -const values = map.values(); -values.map(value => value + 1); -map.keys().toArray(); -const set = new Set(); -set.values().filter(value => value > 0); -set.entries().take(1); +// Iterator helpers lower over Map/Set iterators, generators and 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!(); // Each helper call must produce a diagnostic. diff --git a/tests/diagnostics/json-class-fields.ts b/tests/diagnostics/json-class-fields.ts new file mode 100644 index 000000000..d4d3f8e73 --- /dev/null +++ b/tests/diagnostics/json-class-fields.ts @@ -0,0 +1,22 @@ +// JSON.stringify reads class instances through their checked-dynamic +// property view. A class whose declared field holds a RegExp or a Promise, +// which the runtime JSON traversal cannot write, is refused at compile +// time, naming the field. +class Pattern { + name = "digits"; + source = /\d+/g; +} + +class Job { + title = "fetch"; + pending = Promise.resolve(1); +} + +class Catalog { + title = "team"; + patterns: Pattern[] = [new Pattern()]; +} + +console.log(JSON.stringify(new Pattern())); +console.log(JSON.stringify(new Job())); +console.log(JSON.stringify({ catalog: new Catalog() })); diff --git a/tests/diagnostics/local-class-boundaries.ts b/tests/diagnostics/local-class-boundaries.ts index 5b509931d..a82dfb869 100644 --- a/tests/diagnostics/local-class-boundaries.ts +++ b/tests/diagnostics/local-class-boundaries.ts @@ -7,8 +7,4 @@ privateBrand(); selfName(); derived(); generic(); -function genericFactory(value: T) { - return class Stored { read(): T { return value; } }; -} -genericFactory(7); // End of unsupported local-class forms. diff --git a/tests/diagnostics/local-class-type-query-brand.ts b/tests/diagnostics/local-class-type-query-brand.ts new file mode 100644 index 000000000..566304627 --- /dev/null +++ b/tests/diagnostics/local-class-type-query-brand.ts @@ -0,0 +1,14 @@ +// The same speculative element-type query, reaching a function-local class +// with a private brand through the function's returned array. +function make() { + class Box { + #v = 1; + read(): number { + return this.#v; + } + } + return [new Box()]; +} +const xs = make(); +const x = xs[0]; +console.log(x === undefined ? -1 : x.read()); diff --git a/tests/diagnostics/local-class-type-query-js.js b/tests/diagnostics/local-class-type-query-js.js new file mode 100644 index 000000000..2da619133 --- /dev/null +++ b/tests/diagnostics/local-class-type-query-js.js @@ -0,0 +1,26 @@ +// A whole-program analysis that queries types before reachability is known +// (the computed-key write census here, through `holder.p`) names a +// function-local class whose collection is refused. The refusal is reported, +// once, at the class; it used to crash the compiler with an uncaught +// PoisonError. +function main() { + const Project = class _Project { + constructor() { + this.name = "p"; + } + close() { + if (this.missingFilesMap) { + this.missingFilesMap.clear(); + this.missingFilesMap = void 0; + } + } + add(k) { + (this.missingFilesMap || (this.missingFilesMap = new Map())).set(k, 1); + } + }; + const holder = { p: new Project() }; + const key = process.argv.length > 5 ? String(process.argv.length) : "name"; + holder.p[key] = "q"; + console.log(holder.p.name); +} +main(); diff --git a/tests/diagnostics/local-class-type-query.ts b/tests/diagnostics/local-class-type-query.ts new file mode 100644 index 000000000..217653ff2 --- /dev/null +++ b/tests/diagnostics/local-class-type-query.ts @@ -0,0 +1,14 @@ +// A whole-program analysis queries the element type of `items` before any +// body lowers. The function-local class it names is refused: the refusal is +// reported once, at the class (it used to crash the compiler with an uncaught +// PoisonError). +function make() { + class Box { + static count = 0; + v = 1; + } + return new Box(); +} +const items = [make()]; +const first = items[0]; +console.log(first ? first.v : 0); diff --git a/tests/diagnostics/overloaded-members.ts b/tests/diagnostics/overloaded-members.ts new file mode 100644 index 000000000..07ffa9b63 --- /dev/null +++ b/tests/diagnostics/overloaded-members.ts @@ -0,0 +1,25 @@ +// Overloaded interface members compile as one stored function whose type +// joins the overloads; calls resolve per site. Two fences remain: reading +// the member as a VALUE (its type is still the overload set), and a set +// whose overloads do not join (here, a rest parameter beside a fixed one). +interface Api { + on(event: "a", handler: (x: number) => void): void; + on(event: "b", handler: (x: string) => void): void; +} +const api: Api = { + on(event: string, handler: (x: any) => void): void { + handler(event === "a" ? 1 : "one"); + }, +}; +api.on("a", (x) => console.log(x + 1)); +const detached = api.on; +console.log(typeof detached); + +interface Emitter { + emit(event: "many", ...values: number[]): void; + emit(event: "one", value: string): void; +} +function fire(e: Emitter): void { + e.emit("many", 1, 2); +} +console.log(typeof fire); diff --git a/tests/diagnostics/speculative-function-refusal.ts b/tests/diagnostics/speculative-function-refusal.ts new file mode 100644 index 000000000..fc48d9935 --- /dev/null +++ b/tests/diagnostics/speculative-function-refusal.ts @@ -0,0 +1,14 @@ +// Function expressions lowered speculatively — an `.apply` receiver and a +// computed class base — report a refused statement in their body like any +// other function: the statement never disappears from the build. +(function () { + console.log("wrapper"); + console.log(eval("1 + 1")); +}).apply(null, []); + +class Base {} +class Derived extends (function () { + console.log(new Function("return 1")()); + return Base; +})() {} +console.log(typeof Derived); diff --git a/tests/diagnostics/webassembly-fences.ts b/tests/diagnostics/webassembly-fences.ts new file mode 100644 index 000000000..56b2991de --- /dev/null +++ b/tests/diagnostics/webassembly-fences.ts @@ -0,0 +1,30 @@ +// 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)); + 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); + console.log(WebAssembly.Module.imports(module).length); +} + +// 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])); diff --git a/tests/fixtures/node-test/cases/root-after-throws.ts b/tests/fixtures/node-test/cases/root-after-throws.ts new file mode 100644 index 000000000..f7c20b9b0 --- /dev/null +++ b/tests/fixtures/node-test/cases/root-after-throws.ts @@ -0,0 +1,10 @@ +// A throwing module-level after hook: the run reports the entry file as a +// failed result with the hook's location, after the top-level results, +// without counting it, and the exit code stays 0, as in Node. +import { after, test } from "node:test"; + +after(() => { + throw new Error("root after failed"); +}); + +test("t", () => {}); diff --git a/tests/fixtures/node-test/cases/root-before-throws.ts b/tests/fixtures/node-test/cases/root-before-throws.ts new file mode 100644 index 000000000..b12c50060 --- /dev/null +++ b/tests/fixtures/node-test/cases/root-before-throws.ts @@ -0,0 +1,30 @@ +// @exit: 1 +// A throwing module-level before hook: each top-level test fails with its +// error without running its body or beforeEach hooks, and each top-level +// suite fails with it while the tests and suites in it are cancelled +// before they start. Later module-level before hooks still run at their +// before() calls, and the first failure is the one reported. +import { before, beforeEach, describe, it, test } from "node:test"; + +before(() => { + throw new Error("root before failed"); +}); +before(async () => { + await Promise.resolve(); + throw new Error("a later failure"); +}); +beforeEach(() => { + throw new Error("beforeEach never runs"); +}); + +describe("s", () => { + it("x", () => {}); + describe("inner", () => { + it("y", () => {}); + }); + it("z", () => {}); +}); + +test("t", () => { + throw new Error("the body never runs"); +}); diff --git a/tests/fixtures/node-test/cases/root-hooks.ts b/tests/fixtures/node-test/cases/root-hooks.ts new file mode 100644 index 000000000..8159aa7bb --- /dev/null +++ b/tests/fixtures/node-test/cases/root-hooks.ts @@ -0,0 +1,44 @@ +// Module-level before hooks run at their before() calls, with the root's +// context (named ""), and the first top-level test waits for the +// async ones; module-level after hooks run after the last top-level test +// or suite, and module-level beforeEach hooks run before each test, with +// that test's context. +import { after, before, beforeEach, describe, it, test } from "node:test"; +import * as assert from "node:assert"; + +const seen: string[] = []; + +before((c) => { + seen.push(`before: ${c.name}`); +}); +seen.push("after the before() call"); +before(async () => { + seen.push("async before starts"); + await new Promise((resolve) => setTimeout(resolve, 5)); + seen.push("async before ends"); +}); +beforeEach((c) => { + seen.push(`beforeEach: ${c.name}`); +}); +after(() => { + // Throws, and so fails the run visibly, unless every hook ran in order. + assert.deepStrictEqual(seen, [ + "before: ", + "after the before() call", + "async before starts", + "suite body", + "async before ends", + "beforeEach: x", + "beforeEach: t", + "report", + ]); +}); + +describe("s", () => { + seen.push("suite body"); + it("x", () => {}); +}); + +test("t", () => { + seen.push("report"); +}); diff --git a/tests/fixtures/npm-static/deep-packages-cli.ts b/tests/fixtures/npm-static/deep-packages-cli.ts new file mode 100644 index 000000000..9b10fdf46 --- /dev/null +++ b/tests/fixtures/npm-static/deep-packages-cli.ts @@ -0,0 +1,3 @@ +import { depth } from "hop-1"; + +console.log(depth()); diff --git a/tests/fixtures/npm-static/dup-install-cli.ts b/tests/fixtures/npm-static/dup-install-cli.ts new file mode 100644 index 000000000..346be854d --- /dev/null +++ b/tests/fixtures/npm-static/dup-install-cli.ts @@ -0,0 +1,4 @@ +import { left } from "dup-left"; +import { right } from "dup-right"; + +console.log(left(), left(), right(), left()); diff --git a/tests/fixtures/npm-static/node_modules/@types/twinned/index.d.ts b/tests/fixtures/npm-static/node_modules/@types/twinned/index.d.ts new file mode 100644 index 000000000..ef7c0d6c6 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/@types/twinned/index.d.ts @@ -0,0 +1,5 @@ +export interface Greeting { + text: string; +} + +export default function wait(text: string): string; diff --git a/tests/fixtures/npm-static/node_modules/@types/twinned/package.json b/tests/fixtures/npm-static/node_modules/@types/twinned/package.json new file mode 100644 index 000000000..2bdbbfa6e --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/@types/twinned/package.json @@ -0,0 +1,5 @@ +{ + "name": "@types/twinned", + "version": "1.0.0", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm-static/node_modules/dup-left/index.d.ts b/tests/fixtures/npm-static/node_modules/dup-left/index.d.ts new file mode 100644 index 000000000..f3363e894 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-left/index.d.ts @@ -0,0 +1 @@ +export declare function left(): number; diff --git a/tests/fixtures/npm-static/node_modules/dup-left/index.js b/tests/fixtures/npm-static/node_modules/dup-left/index.js new file mode 100644 index 000000000..ef32a8a0a --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-left/index.js @@ -0,0 +1,5 @@ +const leaf = require("dup-leaf"); +function left() { + return leaf.tick(); +} +exports.left = left; diff --git a/tests/fixtures/npm-static/node_modules/dup-left/node_modules/dup-leaf/index.js b/tests/fixtures/npm-static/node_modules/dup-left/node_modules/dup-leaf/index.js new file mode 100644 index 000000000..9246c7839 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-left/node_modules/dup-leaf/index.js @@ -0,0 +1,6 @@ +let calls = 0; +function tick() { + calls += 1; + return calls; +} +exports.tick = tick; diff --git a/tests/fixtures/npm-static/node_modules/dup-left/node_modules/dup-leaf/package.json b/tests/fixtures/npm-static/node_modules/dup-left/node_modules/dup-leaf/package.json new file mode 100644 index 000000000..f01a0c08f --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-left/node_modules/dup-leaf/package.json @@ -0,0 +1,5 @@ +{ + "name": "dup-leaf", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/dup-left/package.json b/tests/fixtures/npm-static/node_modules/dup-left/package.json new file mode 100644 index 000000000..feab6391f --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-left/package.json @@ -0,0 +1,5 @@ +{ + "name": "dup-left", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/dup-right/index.d.ts b/tests/fixtures/npm-static/node_modules/dup-right/index.d.ts new file mode 100644 index 000000000..51ed905a7 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-right/index.d.ts @@ -0,0 +1 @@ +export declare function right(): number; diff --git a/tests/fixtures/npm-static/node_modules/dup-right/index.js b/tests/fixtures/npm-static/node_modules/dup-right/index.js new file mode 100644 index 000000000..d0bba85ad --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-right/index.js @@ -0,0 +1,5 @@ +const leaf = require("dup-leaf"); +function right() { + return leaf.tick(); +} +exports.right = right; diff --git a/tests/fixtures/npm-static/node_modules/dup-right/node_modules/dup-leaf/index.js b/tests/fixtures/npm-static/node_modules/dup-right/node_modules/dup-leaf/index.js new file mode 100644 index 000000000..9246c7839 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-right/node_modules/dup-leaf/index.js @@ -0,0 +1,6 @@ +let calls = 0; +function tick() { + calls += 1; + return calls; +} +exports.tick = tick; diff --git a/tests/fixtures/npm-static/node_modules/dup-right/node_modules/dup-leaf/package.json b/tests/fixtures/npm-static/node_modules/dup-right/node_modules/dup-leaf/package.json new file mode 100644 index 000000000..f01a0c08f --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-right/node_modules/dup-leaf/package.json @@ -0,0 +1,5 @@ +{ + "name": "dup-leaf", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/dup-right/package.json b/tests/fixtures/npm-static/node_modules/dup-right/package.json new file mode 100644 index 000000000..9889f0261 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/dup-right/package.json @@ -0,0 +1,5 @@ +{ + "name": "dup-right", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-1/index.d.ts b/tests/fixtures/npm-static/node_modules/hop-1/index.d.ts new file mode 100644 index 000000000..4d0af9c97 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-1/index.d.ts @@ -0,0 +1 @@ +export declare function depth(): number; diff --git a/tests/fixtures/npm-static/node_modules/hop-1/index.js b/tests/fixtures/npm-static/node_modules/hop-1/index.js new file mode 100644 index 000000000..57fdb622e --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-1/index.js @@ -0,0 +1,5 @@ +const next = require("hop-2"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-1/package.json b/tests/fixtures/npm-static/node_modules/hop-1/package.json new file mode 100644 index 000000000..9dda7b293 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-1/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-1", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-10/index.js b/tests/fixtures/npm-static/node_modules/hop-10/index.js new file mode 100644 index 000000000..2fa002968 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-10/index.js @@ -0,0 +1,5 @@ +const next = require("hop-11"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-10/package.json b/tests/fixtures/npm-static/node_modules/hop-10/package.json new file mode 100644 index 000000000..624842847 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-10/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-10", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-11/index.js b/tests/fixtures/npm-static/node_modules/hop-11/index.js new file mode 100644 index 000000000..3af42d043 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-11/index.js @@ -0,0 +1,4 @@ +function depth() { + return 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-11/package.json b/tests/fixtures/npm-static/node_modules/hop-11/package.json new file mode 100644 index 000000000..258cf14d9 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-11/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-11", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-2/index.js b/tests/fixtures/npm-static/node_modules/hop-2/index.js new file mode 100644 index 000000000..d21c4aa70 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-2/index.js @@ -0,0 +1,5 @@ +const next = require("hop-3"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-2/package.json b/tests/fixtures/npm-static/node_modules/hop-2/package.json new file mode 100644 index 000000000..268f49b69 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-2/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-2", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-3/index.js b/tests/fixtures/npm-static/node_modules/hop-3/index.js new file mode 100644 index 000000000..83e9c2655 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-3/index.js @@ -0,0 +1,5 @@ +const next = require("hop-4"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-3/package.json b/tests/fixtures/npm-static/node_modules/hop-3/package.json new file mode 100644 index 000000000..68d73b9be --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-3/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-3", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-4/index.js b/tests/fixtures/npm-static/node_modules/hop-4/index.js new file mode 100644 index 000000000..e6af833de --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-4/index.js @@ -0,0 +1,5 @@ +const next = require("hop-5"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-4/package.json b/tests/fixtures/npm-static/node_modules/hop-4/package.json new file mode 100644 index 000000000..35987d11b --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-4/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-4", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-5/index.js b/tests/fixtures/npm-static/node_modules/hop-5/index.js new file mode 100644 index 000000000..e91733984 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-5/index.js @@ -0,0 +1,5 @@ +const next = require("hop-6"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-5/package.json b/tests/fixtures/npm-static/node_modules/hop-5/package.json new file mode 100644 index 000000000..45ee8a1bf --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-5/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-5", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-6/index.js b/tests/fixtures/npm-static/node_modules/hop-6/index.js new file mode 100644 index 000000000..c55749fc8 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-6/index.js @@ -0,0 +1,5 @@ +const next = require("hop-7"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-6/package.json b/tests/fixtures/npm-static/node_modules/hop-6/package.json new file mode 100644 index 000000000..e663924bd --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-6/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-6", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-7/index.js b/tests/fixtures/npm-static/node_modules/hop-7/index.js new file mode 100644 index 000000000..7438cec8d --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-7/index.js @@ -0,0 +1,5 @@ +const next = require("hop-8"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-7/package.json b/tests/fixtures/npm-static/node_modules/hop-7/package.json new file mode 100644 index 000000000..751f50e6c --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-7/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-7", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-8/index.js b/tests/fixtures/npm-static/node_modules/hop-8/index.js new file mode 100644 index 000000000..f60c54f01 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-8/index.js @@ -0,0 +1,5 @@ +const next = require("hop-9"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-8/package.json b/tests/fixtures/npm-static/node_modules/hop-8/package.json new file mode 100644 index 000000000..df486ce40 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-8/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-8", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/hop-9/index.js b/tests/fixtures/npm-static/node_modules/hop-9/index.js new file mode 100644 index 000000000..36861384c --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-9/index.js @@ -0,0 +1,5 @@ +const next = require("hop-10"); +function depth() { + return next.depth() + 1; +} +exports.depth = depth; diff --git a/tests/fixtures/npm-static/node_modules/hop-9/package.json b/tests/fixtures/npm-static/node_modules/hop-9/package.json new file mode 100644 index 000000000..5a6ffeb84 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/hop-9/package.json @@ -0,0 +1,5 @@ +{ + "name": "hop-9", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/node_modules/split-node-types/dist-node/build.js b/tests/fixtures/npm-static/node_modules/split-node-types/dist-node/build.js new file mode 100644 index 000000000..a155b52d4 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/split-node-types/dist-node/build.js @@ -0,0 +1,3 @@ +export default function build() { + return "node build"; +} diff --git a/tests/fixtures/npm-static/node_modules/split-node-types/dist-node/index.js b/tests/fixtures/npm-static/node_modules/split-node-types/dist-node/index.js new file mode 100644 index 000000000..1f79b2f93 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/split-node-types/dist-node/index.js @@ -0,0 +1,2 @@ +export { default as build } from "./build.js"; +export const NIL = "node"; diff --git a/tests/fixtures/npm-static/node_modules/split-node-types/dist/index.d.ts b/tests/fixtures/npm-static/node_modules/split-node-types/dist/index.d.ts new file mode 100644 index 000000000..47272b882 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/split-node-types/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function build(): string; +export declare const NIL: string; diff --git a/tests/fixtures/npm-static/node_modules/split-node-types/dist/index.js b/tests/fixtures/npm-static/node_modules/split-node-types/dist/index.js new file mode 100644 index 000000000..308044e34 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/split-node-types/dist/index.js @@ -0,0 +1,4 @@ +export function build() { + return "browser build"; +} +export const NIL = "browser"; diff --git a/tests/fixtures/npm-static/node_modules/split-node-types/package.json b/tests/fixtures/npm-static/node_modules/split-node-types/package.json new file mode 100644 index 000000000..aaa29cebc --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/split-node-types/package.json @@ -0,0 +1,16 @@ +{ + "name": "split-node-types", + "version": "1.0.0", + "type": "module", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "node": { + "types": "./dist/index.d.ts", + "default": "./dist-node/index.js" + }, + "default": "./dist/index.js" + }, + "./package.json": "./package.json" + } +} diff --git a/tests/fixtures/npm-static/node_modules/ts-root-source/dist/cjs/index.js b/tests/fixtures/npm-static/node_modules/ts-root-source/dist/cjs/index.js new file mode 100644 index 000000000..aacc3bcce --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/ts-root-source/dist/cjs/index.js @@ -0,0 +1,6 @@ +"use strict"; +const domain = require("./src/domain.js"); +function getDomain(host) { + return domain.lastTwo(host); +} +exports.getDomain = getDomain; diff --git a/tests/fixtures/npm-static/node_modules/ts-root-source/dist/cjs/src/domain.js b/tests/fixtures/npm-static/node_modules/ts-root-source/dist/cjs/src/domain.js new file mode 100644 index 000000000..98b461afa --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/ts-root-source/dist/cjs/src/domain.js @@ -0,0 +1,5 @@ +"use strict"; +function lastTwo(host) { + return host.split(".").slice(-2).join("."); +} +exports.lastTwo = lastTwo; diff --git a/tests/fixtures/npm-static/node_modules/ts-root-source/dist/types/index.d.ts b/tests/fixtures/npm-static/node_modules/ts-root-source/dist/types/index.d.ts new file mode 100644 index 000000000..dac3eccb0 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/ts-root-source/dist/types/index.d.ts @@ -0,0 +1 @@ +export declare function getDomain(host: string): string; diff --git a/tests/fixtures/npm-static/node_modules/ts-root-source/index.ts b/tests/fixtures/npm-static/node_modules/ts-root-source/index.ts new file mode 100644 index 000000000..de03041f8 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/ts-root-source/index.ts @@ -0,0 +1 @@ +export { getDomain } from "./src/domain"; diff --git a/tests/fixtures/npm-static/node_modules/ts-root-source/package.json b/tests/fixtures/npm-static/node_modules/ts-root-source/package.json new file mode 100644 index 000000000..edfff933b --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/ts-root-source/package.json @@ -0,0 +1,7 @@ +{ + "name": "ts-root-source", + "version": "1.0.0", + "main": "dist/cjs/index.js", + "module": "dist/es6/index.js", + "types": "dist/types/index.d.ts" +} diff --git a/tests/fixtures/npm-static/node_modules/ts-root-source/src/domain.ts b/tests/fixtures/npm-static/node_modules/ts-root-source/src/domain.ts new file mode 100644 index 000000000..fe1e5eb18 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/ts-root-source/src/domain.ts @@ -0,0 +1,3 @@ +export function getDomain(host: string): string { + return "TypeScript source: " + host; +} diff --git a/tests/fixtures/npm-static/node_modules/twinned/index.js b/tests/fixtures/npm-static/node_modules/twinned/index.js new file mode 100644 index 000000000..0aa0a79f0 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/twinned/index.js @@ -0,0 +1,9 @@ +// A CommonJS package without its own declarations: its types live in the +// separate @types/twinned package, and it depends on the untyped ms. +'use strict'; + +const ms = require('ms'); + +module.exports = function wait(text) { + return 'twinned:' + text + ':' + ms(90000); +}; diff --git a/tests/fixtures/npm-static/node_modules/twinned/package.json b/tests/fixtures/npm-static/node_modules/twinned/package.json new file mode 100644 index 000000000..14cce5ab2 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/twinned/package.json @@ -0,0 +1,5 @@ +{ + "name": "twinned", + "version": "1.0.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/split-node-types-cli.ts b/tests/fixtures/npm-static/split-node-types-cli.ts new file mode 100644 index 000000000..ecfac96ba --- /dev/null +++ b/tests/fixtures/npm-static/split-node-types-cli.ts @@ -0,0 +1,3 @@ +import { build, NIL } from "split-node-types"; + +console.log(build(), NIL); diff --git a/tests/fixtures/npm-static/ts-root-source-cli.ts b/tests/fixtures/npm-static/ts-root-source-cli.ts new file mode 100644 index 000000000..3741b48e5 --- /dev/null +++ b/tests/fixtures/npm-static/ts-root-source-cli.ts @@ -0,0 +1,3 @@ +import { getDomain } from "ts-root-source"; + +console.log(getDomain("a.b.github.io")); diff --git a/tests/fixtures/npm-static/twinned-cli.ts b/tests/fixtures/npm-static/twinned-cli.ts new file mode 100644 index 000000000..e24383184 --- /dev/null +++ b/tests/fixtures/npm-static/twinned-cli.ts @@ -0,0 +1,5 @@ +// twinned's declarations come from @types/twinned; the package itself and +// its untyped ms dependency ship JavaScript only. +import wait from "twinned"; + +console.log(wait("ready")); diff --git a/tests/fixtures/npm-static/type-only-cli.ts b/tests/fixtures/npm-static/type-only-cli.ts new file mode 100644 index 000000000..9fd332331 --- /dev/null +++ b/tests/fixtures/npm-static/type-only-cli.ts @@ -0,0 +1,7 @@ +// `import type` erases under Node's type stripping: twinned is never +// loaded, so no package is selected or refused, and its declared +// interface types a value the program builds itself. +import type { Greeting } from "twinned"; + +const greeting: Greeting = { text: "hello" }; +console.log(greeting.text); diff --git a/tests/fixtures/npm/cases/chalk5/main.ts b/tests/fixtures/npm/cases/chalk5/main.ts new file mode 100644 index 000000000..6a33a1466 --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/main.ts @@ -0,0 +1,64 @@ +// chalk 5 (the real package, pinned in node_modules): the callable +// builder whose styles are prototype getters on a function chain, every +// color model, nested styles, chalk.level and new Chalk({ level }) at all +// four levels. chalk.test.ts runs the same program under FORCE_COLOR and +// the --color flags. +import chalk, { Chalk, chalkStderr, supportsColor, modifierNames, foregroundColorNames, backgroundColorNames, colorNames } from "chalk"; + +const show = (label: string, value: string): void => { + console.log(label + " " + JSON.stringify(value)); +}; + +console.log("default level " + String(chalk.level) + " stderr " + String(chalkStderr.level)); +console.log("supportsColor " + JSON.stringify(supportsColor)); +console.log(modifierNames.join(",")); +console.log(foregroundColorNames.length + " " + backgroundColorNames.length + " " + colorNames.length); + +show("red", chalk.red("red")); +show("red.bold", chalk.red.bold("bold red")); +show("nested", chalk.blue("a " + chalk.red("b") + " c")); +show("multi-arg", chalk.green("one", "two", 3)); +show("number", chalk.yellow(42 as unknown as string)); +show("empty", chalk.red("")); +show("newline", chalk.magenta.underline("line1\nline2\r\nline3")); +show("bg", chalk.bgCyan.black("bg")); +show("bright", chalk.redBright.bgBlueBright("bright")); +show("modifiers", chalk.italic.inverse.strikethrough.overline("mods")); +show("hidden-visible", chalk.hidden("h") + "|" + chalk.visible("v")); +show("rgb", chalk.rgb(10, 20, 30)("rgb")); +show("hex", chalk.hex("#ff8800").bold("hex")); +show("hex-short", chalk.bgHex("#0f0")("bg hex")); +show("ansi256", chalk.ansi256(201)("ansi256")); +show("bgAnsi256", chalk.bgAnsi256(17).white("bgAnsi256")); +show("callable", chalk("plain", "args")); +show("gray-grey", chalk.gray("x") + chalk.grey("y")); + +for (const level of [0, 1, 2, 3] as const) { + const c = new Chalk({ level }); + show(`L${level} red.bold`, c.red.bold("x")); + show(`L${level} hex`, c.hex("#123456")("x")); + show(`L${level} rgb`, c.rgb(255, 255, 255).bgRgb(0, 0, 0)("x")); + show(`L${level} ansi256`, c.ansi256(100).bgAnsi256(200)("x")); + show(`L${level} visible`, c.visible.red("v")); + show(`L${level} nested`, c.green("g" + c.yellow("y") + "g")); +} + +const mutable = new Chalk({ level: 3 }); +const red = mutable.red; +show("before", red("r")); +mutable.level = 0; +show("after level 0", red("r") + "|" + mutable.blue("b")); +console.log("level via builder " + String(red.level)); +red.level = 2; +console.log("shared level " + String(mutable.level)); +show("restored", mutable.cyan("c")); + +try { + new Chalk({ level: 5 as 0 }); +} catch (error) { + console.log("error " + (error as Error).message); +} + +chalk.level = 1; +show("global after set", chalk.red.bold("x")); +console.log("global level " + String(chalk.level)); diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/license b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/license new file mode 100644 index 000000000..fa7ceba3e --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/license @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +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/fixtures/npm/cases/chalk5/node_modules/chalk/package.json b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/package.json new file mode 100644 index 000000000..c9e0dc52b --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/package.json @@ -0,0 +1,83 @@ +{ + "name": "chalk", + "version": "5.6.2", + "description": "Terminal string styling done right", + "license": "MIT", + "repository": "chalk/chalk", + "funding": "https://github.com/chalk/chalk?sponsor=1", + "type": "module", + "main": "./source/index.js", + "exports": "./source/index.js", + "imports": { + "#ansi-styles": "./source/vendor/ansi-styles/index.js", + "#supports-color": { + "node": "./source/vendor/supports-color/index.js", + "default": "./source/vendor/supports-color/browser.js" + } + }, + "types": "./source/index.d.ts", + "sideEffects": false, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "scripts": { + "test": "xo && c8 ava && tsd", + "bench": "matcha benchmark.js" + }, + "files": [ + "source", + "!source/index.test-d.ts" + ], + "keywords": [ + "color", + "colour", + "colors", + "terminal", + "console", + "cli", + "string", + "ansi", + "style", + "styles", + "tty", + "formatting", + "rgb", + "256", + "shell", + "xterm", + "log", + "logging", + "command-line", + "text" + ], + "devDependencies": { + "@types/node": "^16.11.10", + "ava": "^3.15.0", + "c8": "^7.10.0", + "color-convert": "^2.0.1", + "execa": "^6.0.0", + "log-update": "^5.0.0", + "matcha": "^0.7.0", + "tsd": "^0.19.0", + "xo": "^0.57.0", + "yoctodelay": "^2.0.0" + }, + "xo": { + "rules": { + "unicorn/prefer-string-slice": "off", + "@typescript-eslint/consistent-type-imports": "off", + "@typescript-eslint/consistent-type-exports": "off", + "@typescript-eslint/consistent-type-definitions": "off", + "unicorn/expiring-todo-comments": "off" + } + }, + "c8": { + "reporter": [ + "text", + "lcov" + ], + "exclude": [ + "source/vendor" + ] + } +} diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/index.d.ts b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/index.d.ts new file mode 100644 index 000000000..8295d9252 --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/index.d.ts @@ -0,0 +1,325 @@ +// TODO: Make it this when TS suports that. +// import {ModifierName, ForegroundColor, BackgroundColor, ColorName} from '#ansi-styles'; +// import {ColorInfo, ColorSupportLevel} from '#supports-color'; +import { + ModifierName, + ForegroundColorName, + BackgroundColorName, + ColorName, +} from './vendor/ansi-styles/index.js'; +import {ColorInfo, ColorSupportLevel} from './vendor/supports-color/index.js'; + +export interface Options { + /** + Specify the color support for Chalk. + + By default, color support is automatically detected based on the environment. + + Levels: + - `0` - All colors disabled. + - `1` - Basic 16 colors support. + - `2` - ANSI 256 colors support. + - `3` - Truecolor 16 million colors support. + */ + readonly level?: ColorSupportLevel; +} + +/** +Return a new Chalk instance. +*/ +export const Chalk: new (options?: Options) => ChalkInstance; // eslint-disable-line @typescript-eslint/naming-convention + +export interface ChalkInstance { + (...text: unknown[]): string; + + /** + The color support for Chalk. + + By default, color support is automatically detected based on the environment. + + Levels: + - `0` - All colors disabled. + - `1` - Basic 16 colors support. + - `2` - ANSI 256 colors support. + - `3` - Truecolor 16 million colors support. + */ + level: ColorSupportLevel; + + /** + Use RGB values to set text color. + + @example + ``` + import chalk from 'chalk'; + + chalk.rgb(222, 173, 237); + ``` + */ + rgb: (red: number, green: number, blue: number) => this; + + /** + Use HEX value to set text color. + + @param color - Hexadecimal value representing the desired color. + + @example + ``` + import chalk from 'chalk'; + + chalk.hex('#DEADED'); + ``` + */ + hex: (color: string) => this; + + /** + Use an [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set text color. + + @example + ``` + import chalk from 'chalk'; + + chalk.ansi256(201); + ``` + */ + ansi256: (index: number) => this; + + /** + Use RGB values to set background color. + + @example + ``` + import chalk from 'chalk'; + + chalk.bgRgb(222, 173, 237); + ``` + */ + bgRgb: (red: number, green: number, blue: number) => this; + + /** + Use HEX value to set background color. + + @param color - Hexadecimal value representing the desired color. + + @example + ``` + import chalk from 'chalk'; + + chalk.bgHex('#DEADED'); + ``` + */ + bgHex: (color: string) => this; + + /** + Use a [8-bit unsigned number](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) to set background color. + + @example + ``` + import chalk from 'chalk'; + + chalk.bgAnsi256(201); + ``` + */ + bgAnsi256: (index: number) => this; + + /** + Modifier: Reset the current style. + */ + readonly reset: this; + + /** + Modifier: Make the text bold. + */ + readonly bold: this; + + /** + Modifier: Make the text have lower opacity. + */ + readonly dim: this; + + /** + Modifier: Make the text italic. *(Not widely supported)* + */ + readonly italic: this; + + /** + Modifier: Put a horizontal line below the text. *(Not widely supported)* + */ + readonly underline: this; + + /** + Modifier: Put a horizontal line above the text. *(Not widely supported)* + */ + readonly overline: this; + + /** + Modifier: Invert background and foreground colors. + */ + readonly inverse: this; + + /** + Modifier: Print the text but make it invisible. + */ + readonly hidden: this; + + /** + Modifier: Puts a horizontal line through the center of the text. *(Not widely supported)* + */ + readonly strikethrough: this; + + /** + Modifier: Print the text only when Chalk has a color level above zero. + + Can be useful for things that are purely cosmetic. + */ + readonly visible: this; + + readonly black: this; + readonly red: this; + readonly green: this; + readonly yellow: this; + readonly blue: this; + readonly magenta: this; + readonly cyan: this; + readonly white: this; + + /* + Alias for `blackBright`. + */ + readonly gray: this; + + /* + Alias for `blackBright`. + */ + readonly grey: this; + + readonly blackBright: this; + readonly redBright: this; + readonly greenBright: this; + readonly yellowBright: this; + readonly blueBright: this; + readonly magentaBright: this; + readonly cyanBright: this; + readonly whiteBright: this; + + readonly bgBlack: this; + readonly bgRed: this; + readonly bgGreen: this; + readonly bgYellow: this; + readonly bgBlue: this; + readonly bgMagenta: this; + readonly bgCyan: this; + readonly bgWhite: this; + + /* + Alias for `bgBlackBright`. + */ + readonly bgGray: this; + + /* + Alias for `bgBlackBright`. + */ + readonly bgGrey: this; + + readonly bgBlackBright: this; + readonly bgRedBright: this; + readonly bgGreenBright: this; + readonly bgYellowBright: this; + readonly bgBlueBright: this; + readonly bgMagentaBright: this; + readonly bgCyanBright: this; + readonly bgWhiteBright: this; +} + +/** +Main Chalk object that allows to chain styles together. + +Call the last one as a method with a string argument. + +Order doesn't matter, and later styles take precedent in case of a conflict. + +This simply means that `chalk.red.yellow.green` is equivalent to `chalk.green`. +*/ +declare const chalk: ChalkInstance; + +export const supportsColor: ColorInfo; + +export const chalkStderr: typeof chalk; +export const supportsColorStderr: typeof supportsColor; + +export { + ModifierName, ForegroundColorName, BackgroundColorName, ColorName, + modifierNames, foregroundColorNames, backgroundColorNames, colorNames, +// } from '#ansi-styles'; +} from './vendor/ansi-styles/index.js'; + +export { + ColorInfo, + ColorSupport, + ColorSupportLevel, +// } from '#supports-color'; +} from './vendor/supports-color/index.js'; + +// TODO: Remove these aliases in the next major version +/** +@deprecated Use `ModifierName` instead. + +Basic modifier names. +*/ +export type Modifiers = ModifierName; + +/** +@deprecated Use `ForegroundColorName` instead. + +Basic foreground color names. + +[More colors here.](https://github.com/chalk/chalk/blob/main/readme.md#256-and-truecolor-color-support) +*/ +export type ForegroundColor = ForegroundColorName; + +/** +@deprecated Use `BackgroundColorName` instead. + +Basic background color names. + +[More colors here.](https://github.com/chalk/chalk/blob/main/readme.md#256-and-truecolor-color-support) +*/ +export type BackgroundColor = BackgroundColorName; + +/** +@deprecated Use `ColorName` instead. + +Basic color names. The combination of foreground and background color names. + +[More colors here.](https://github.com/chalk/chalk/blob/main/readme.md#256-and-truecolor-color-support) +*/ +export type Color = ColorName; + +/** +@deprecated Use `modifierNames` instead. + +Basic modifier names. +*/ +export const modifiers: readonly Modifiers[]; + +/** +@deprecated Use `foregroundColorNames` instead. + +Basic foreground color names. +*/ +export const foregroundColors: readonly ForegroundColor[]; + +/** +@deprecated Use `backgroundColorNames` instead. + +Basic background color names. +*/ +export const backgroundColors: readonly BackgroundColor[]; + +/** +@deprecated Use `colorNames` instead. + +Basic color names. The combination of foreground and background color names. +*/ +export const colors: readonly Color[]; + +export default chalk; diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/index.js b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/index.js new file mode 100644 index 000000000..8bc993da5 --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/index.js @@ -0,0 +1,225 @@ +import ansiStyles from '#ansi-styles'; +import supportsColor from '#supports-color'; +import { // eslint-disable-line import/order + stringReplaceAll, + stringEncaseCRLFWithFirstIndex, +} from './utilities.js'; + +const {stdout: stdoutColor, stderr: stderrColor} = supportsColor; + +const GENERATOR = Symbol('GENERATOR'); +const STYLER = Symbol('STYLER'); +const IS_EMPTY = Symbol('IS_EMPTY'); + +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = [ + 'ansi', + 'ansi', + 'ansi256', + 'ansi16m', +]; + +const styles = Object.create(null); + +const applyOptions = (object, options = {}) => { + if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) { + throw new Error('The `level` option should be an integer from 0 to 3'); + } + + // Detect level if not set manually + const colorLevel = stdoutColor ? stdoutColor.level : 0; + object.level = options.level === undefined ? colorLevel : options.level; +}; + +export class Chalk { + constructor(options) { + // eslint-disable-next-line no-constructor-return + return chalkFactory(options); + } +} + +const chalkFactory = options => { + const chalk = (...strings) => strings.join(' '); + applyOptions(chalk, options); + + Object.setPrototypeOf(chalk, createChalk.prototype); + + return chalk; +}; + +function createChalk(options) { + return chalkFactory(options); +} + +Object.setPrototypeOf(createChalk.prototype, Function.prototype); + +for (const [styleName, style] of Object.entries(ansiStyles)) { + styles[styleName] = { + get() { + const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]); + Object.defineProperty(this, styleName, {value: builder}); + return builder; + }, + }; +} + +styles.visible = { + get() { + const builder = createBuilder(this, this[STYLER], true); + Object.defineProperty(this, 'visible', {value: builder}); + return builder; + }, +}; + +const getModelAnsi = (model, level, type, ...arguments_) => { + if (model === 'rgb') { + if (level === 'ansi16m') { + return ansiStyles[type].ansi16m(...arguments_); + } + + if (level === 'ansi256') { + return ansiStyles[type].ansi256(ansiStyles.rgbToAnsi256(...arguments_)); + } + + return ansiStyles[type].ansi(ansiStyles.rgbToAnsi(...arguments_)); + } + + if (model === 'hex') { + return getModelAnsi('rgb', level, type, ...ansiStyles.hexToRgb(...arguments_)); + } + + return ansiStyles[type][model](...arguments_); +}; + +const usedModels = ['rgb', 'hex', 'ansi256']; + +for (const model of usedModels) { + styles[model] = { + get() { + const {level} = this; + return function (...arguments_) { + const styler = createStyler(getModelAnsi(model, levelMapping[level], 'color', ...arguments_), ansiStyles.color.close, this[STYLER]); + return createBuilder(this, styler, this[IS_EMPTY]); + }; + }, + }; + + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const {level} = this; + return function (...arguments_) { + const styler = createStyler(getModelAnsi(model, levelMapping[level], 'bgColor', ...arguments_), ansiStyles.bgColor.close, this[STYLER]); + return createBuilder(this, styler, this[IS_EMPTY]); + }; + }, + }; +} + +const proto = Object.defineProperties(() => {}, { + ...styles, + level: { + enumerable: true, + get() { + return this[GENERATOR].level; + }, + set(level) { + this[GENERATOR].level = level; + }, + }, +}); + +const createStyler = (open, close, parent) => { + let openAll; + let closeAll; + if (parent === undefined) { + openAll = open; + closeAll = close; + } else { + openAll = parent.openAll + open; + closeAll = close + parent.closeAll; + } + + return { + open, + close, + openAll, + closeAll, + parent, + }; +}; + +const createBuilder = (self, _styler, _isEmpty) => { + // Single argument is hot path, implicit coercion is faster than anything + // eslint-disable-next-line no-implicit-coercion + const builder = (...arguments_) => applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' ')); + + // We alter the prototype because we must return a function, but there is + // no way to create a function with a different prototype + Object.setPrototypeOf(builder, proto); + + builder[GENERATOR] = self; + builder[STYLER] = _styler; + builder[IS_EMPTY] = _isEmpty; + + return builder; +}; + +const applyStyle = (self, string) => { + if (self.level <= 0 || !string) { + return self[IS_EMPTY] ? '' : string; + } + + let styler = self[STYLER]; + + if (styler === undefined) { + return string; + } + + const {openAll, closeAll} = styler; + if (string.includes('\u001B')) { + while (styler !== undefined) { + // Replace any instances already present with a re-opening code + // otherwise only the part of the string until said closing code + // will be colored, and the rest will simply be 'plain'. + string = stringReplaceAll(string, styler.close, styler.open); + + styler = styler.parent; + } + } + + // We can move both next actions out of loop, because remaining actions in loop won't have + // any/visible effect on parts we add here. Close the styling before a linebreak and reopen + // after next line to fix a bleed issue on macOS: https://github.com/chalk/chalk/pull/92 + const lfIndex = string.indexOf('\n'); + if (lfIndex !== -1) { + string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); + } + + return openAll + string + closeAll; +}; + +Object.defineProperties(createChalk.prototype, styles); + +const chalk = createChalk(); +export const chalkStderr = createChalk({level: stderrColor ? stderrColor.level : 0}); + +export { + modifierNames, + foregroundColorNames, + backgroundColorNames, + colorNames, + + // TODO: Remove these aliases in the next major version + modifierNames as modifiers, + foregroundColorNames as foregroundColors, + backgroundColorNames as backgroundColors, + colorNames as colors, +} from './vendor/ansi-styles/index.js'; + +export { + stdoutColor as supportsColor, + stderrColor as supportsColorStderr, +}; + +export default chalk; diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/utilities.js b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/utilities.js new file mode 100644 index 000000000..4366dee0d --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/utilities.js @@ -0,0 +1,33 @@ +// TODO: When targeting Node.js 16, use `String.prototype.replaceAll`. +export function stringReplaceAll(string, substring, replacer) { + let index = string.indexOf(substring); + if (index === -1) { + return string; + } + + const substringLength = substring.length; + let endIndex = 0; + let returnValue = ''; + do { + returnValue += string.slice(endIndex, index) + substring + replacer; + endIndex = index + substringLength; + index = string.indexOf(substring, endIndex); + } while (index !== -1); + + returnValue += string.slice(endIndex); + return returnValue; +} + +export function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) { + let endIndex = 0; + let returnValue = ''; + do { + const gotCR = string[index - 1] === '\r'; + returnValue += string.slice(endIndex, (gotCR ? index - 1 : index)) + prefix + (gotCR ? '\r\n' : '\n') + postfix; + endIndex = index + 1; + index = string.indexOf('\n', endIndex); + } while (index !== -1); + + returnValue += string.slice(endIndex); + return returnValue; +} diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/ansi-styles/index.d.ts b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/ansi-styles/index.d.ts new file mode 100644 index 000000000..58f133abe --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/ansi-styles/index.d.ts @@ -0,0 +1,236 @@ +export interface CSPair { // eslint-disable-line @typescript-eslint/naming-convention + /** + The ANSI terminal control sequence for starting this style. + */ + readonly open: string; + + /** + The ANSI terminal control sequence for ending this style. + */ + readonly close: string; +} + +export interface ColorBase { + /** + The ANSI terminal control sequence for ending this color. + */ + readonly close: string; + + ansi(code: number): string; + + ansi256(code: number): string; + + ansi16m(red: number, green: number, blue: number): string; +} + +export interface Modifier { + /** + Resets the current color chain. + */ + readonly reset: CSPair; + + /** + Make text bold. + */ + readonly bold: CSPair; + + /** + Emitting only a small amount of light. + */ + readonly dim: CSPair; + + /** + Make text italic. (Not widely supported) + */ + readonly italic: CSPair; + + /** + Make text underline. (Not widely supported) + */ + readonly underline: CSPair; + + /** + Make text overline. + + Supported on VTE-based terminals, the GNOME terminal, mintty, and Git Bash. + */ + readonly overline: CSPair; + + /** + Inverse background and foreground colors. + */ + readonly inverse: CSPair; + + /** + Prints the text, but makes it invisible. + */ + readonly hidden: CSPair; + + /** + Puts a horizontal line through the center of the text. (Not widely supported) + */ + readonly strikethrough: CSPair; +} + +export interface ForegroundColor { + readonly black: CSPair; + readonly red: CSPair; + readonly green: CSPair; + readonly yellow: CSPair; + readonly blue: CSPair; + readonly cyan: CSPair; + readonly magenta: CSPair; + readonly white: CSPair; + + /** + Alias for `blackBright`. + */ + readonly gray: CSPair; + + /** + Alias for `blackBright`. + */ + readonly grey: CSPair; + + readonly blackBright: CSPair; + readonly redBright: CSPair; + readonly greenBright: CSPair; + readonly yellowBright: CSPair; + readonly blueBright: CSPair; + readonly cyanBright: CSPair; + readonly magentaBright: CSPair; + readonly whiteBright: CSPair; +} + +export interface BackgroundColor { + readonly bgBlack: CSPair; + readonly bgRed: CSPair; + readonly bgGreen: CSPair; + readonly bgYellow: CSPair; + readonly bgBlue: CSPair; + readonly bgCyan: CSPair; + readonly bgMagenta: CSPair; + readonly bgWhite: CSPair; + + /** + Alias for `bgBlackBright`. + */ + readonly bgGray: CSPair; + + /** + Alias for `bgBlackBright`. + */ + readonly bgGrey: CSPair; + + readonly bgBlackBright: CSPair; + readonly bgRedBright: CSPair; + readonly bgGreenBright: CSPair; + readonly bgYellowBright: CSPair; + readonly bgBlueBright: CSPair; + readonly bgCyanBright: CSPair; + readonly bgMagentaBright: CSPair; + readonly bgWhiteBright: CSPair; +} + +export interface ConvertColor { + /** + Convert from the RGB color space to the ANSI 256 color space. + + @param red - (`0...255`) + @param green - (`0...255`) + @param blue - (`0...255`) + */ + rgbToAnsi256(red: number, green: number, blue: number): number; + + /** + Convert from the RGB HEX color space to the RGB color space. + + @param hex - A hexadecimal string containing RGB data. + */ + hexToRgb(hex: string): [red: number, green: number, blue: number]; + + /** + Convert from the RGB HEX color space to the ANSI 256 color space. + + @param hex - A hexadecimal string containing RGB data. + */ + hexToAnsi256(hex: string): number; + + /** + Convert from the ANSI 256 color space to the ANSI 16 color space. + + @param code - A number representing the ANSI 256 color. + */ + ansi256ToAnsi(code: number): number; + + /** + Convert from the RGB color space to the ANSI 16 color space. + + @param red - (`0...255`) + @param green - (`0...255`) + @param blue - (`0...255`) + */ + rgbToAnsi(red: number, green: number, blue: number): number; + + /** + Convert from the RGB HEX color space to the ANSI 16 color space. + + @param hex - A hexadecimal string containing RGB data. + */ + hexToAnsi(hex: string): number; +} + +/** +Basic modifier names. +*/ +export type ModifierName = keyof Modifier; + +/** +Basic foreground color names. + +[More colors here.](https://github.com/chalk/chalk/blob/main/readme.md#256-and-truecolor-color-support) +*/ +export type ForegroundColorName = keyof ForegroundColor; + +/** +Basic background color names. + +[More colors here.](https://github.com/chalk/chalk/blob/main/readme.md#256-and-truecolor-color-support) +*/ +export type BackgroundColorName = keyof BackgroundColor; + +/** +Basic color names. The combination of foreground and background color names. + +[More colors here.](https://github.com/chalk/chalk/blob/main/readme.md#256-and-truecolor-color-support) +*/ +export type ColorName = ForegroundColorName | BackgroundColorName; + +/** +Basic modifier names. +*/ +export const modifierNames: readonly ModifierName[]; + +/** +Basic foreground color names. +*/ +export const foregroundColorNames: readonly ForegroundColorName[]; + +/** +Basic background color names. +*/ +export const backgroundColorNames: readonly BackgroundColorName[]; + +/* +Basic color names. The combination of foreground and background color names. +*/ +export const colorNames: readonly ColorName[]; + +declare const ansiStyles: { + readonly modifier: Modifier; + readonly color: ColorBase & ForegroundColor; + readonly bgColor: ColorBase & BackgroundColor; + readonly codes: ReadonlyMap; +} & ForegroundColor & BackgroundColor & Modifier & ConvertColor; + +export default ansiStyles; diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/ansi-styles/index.js b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/ansi-styles/index.js new file mode 100644 index 000000000..eaa7bed6c --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/ansi-styles/index.js @@ -0,0 +1,223 @@ +const ANSI_BACKGROUND_OFFSET = 10; + +const wrapAnsi16 = (offset = 0) => code => `\u001B[${code + offset}m`; + +const wrapAnsi256 = (offset = 0) => code => `\u001B[${38 + offset};5;${code}m`; + +const wrapAnsi16m = (offset = 0) => (red, green, blue) => `\u001B[${38 + offset};2;${red};${green};${blue}m`; + +const styles = { + modifier: { + reset: [0, 0], + // 21 isn't widely supported and 22 does the same thing + bold: [1, 22], + dim: [2, 22], + italic: [3, 23], + underline: [4, 24], + overline: [53, 55], + inverse: [7, 27], + hidden: [8, 28], + strikethrough: [9, 29], + }, + color: { + black: [30, 39], + red: [31, 39], + green: [32, 39], + yellow: [33, 39], + blue: [34, 39], + magenta: [35, 39], + cyan: [36, 39], + white: [37, 39], + + // Bright color + blackBright: [90, 39], + gray: [90, 39], // Alias of `blackBright` + grey: [90, 39], // Alias of `blackBright` + redBright: [91, 39], + greenBright: [92, 39], + yellowBright: [93, 39], + blueBright: [94, 39], + magentaBright: [95, 39], + cyanBright: [96, 39], + whiteBright: [97, 39], + }, + bgColor: { + bgBlack: [40, 49], + bgRed: [41, 49], + bgGreen: [42, 49], + bgYellow: [43, 49], + bgBlue: [44, 49], + bgMagenta: [45, 49], + bgCyan: [46, 49], + bgWhite: [47, 49], + + // Bright color + bgBlackBright: [100, 49], + bgGray: [100, 49], // Alias of `bgBlackBright` + bgGrey: [100, 49], // Alias of `bgBlackBright` + bgRedBright: [101, 49], + bgGreenBright: [102, 49], + bgYellowBright: [103, 49], + bgBlueBright: [104, 49], + bgMagentaBright: [105, 49], + bgCyanBright: [106, 49], + bgWhiteBright: [107, 49], + }, +}; + +export const modifierNames = Object.keys(styles.modifier); +export const foregroundColorNames = Object.keys(styles.color); +export const backgroundColorNames = Object.keys(styles.bgColor); +export const colorNames = [...foregroundColorNames, ...backgroundColorNames]; + +function assembleStyles() { + const codes = new Map(); + + for (const [groupName, group] of Object.entries(styles)) { + for (const [styleName, style] of Object.entries(group)) { + styles[styleName] = { + open: `\u001B[${style[0]}m`, + close: `\u001B[${style[1]}m`, + }; + + group[styleName] = styles[styleName]; + + codes.set(style[0], style[1]); + } + + Object.defineProperty(styles, groupName, { + value: group, + enumerable: false, + }); + } + + Object.defineProperty(styles, 'codes', { + value: codes, + enumerable: false, + }); + + styles.color.close = '\u001B[39m'; + styles.bgColor.close = '\u001B[49m'; + + styles.color.ansi = wrapAnsi16(); + styles.color.ansi256 = wrapAnsi256(); + styles.color.ansi16m = wrapAnsi16m(); + styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET); + styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET); + styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET); + + // From https://github.com/Qix-/color-convert/blob/3f0e0d4e92e235796ccb17f6e85c72094a651f49/conversions.js + Object.defineProperties(styles, { + rgbToAnsi256: { + value(red, green, blue) { + // We use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (red === green && green === blue) { + if (red < 8) { + return 16; + } + + if (red > 248) { + return 231; + } + + return Math.round(((red - 8) / 247) * 24) + 232; + } + + return 16 + + (36 * Math.round(red / 255 * 5)) + + (6 * Math.round(green / 255 * 5)) + + Math.round(blue / 255 * 5); + }, + enumerable: false, + }, + hexToRgb: { + value(hex) { + const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16)); + if (!matches) { + return [0, 0, 0]; + } + + let [colorString] = matches; + + if (colorString.length === 3) { + colorString = [...colorString].map(character => character + character).join(''); + } + + const integer = Number.parseInt(colorString, 16); + + return [ + /* eslint-disable no-bitwise */ + (integer >> 16) & 0xFF, + (integer >> 8) & 0xFF, + integer & 0xFF, + /* eslint-enable no-bitwise */ + ]; + }, + enumerable: false, + }, + hexToAnsi256: { + value: hex => styles.rgbToAnsi256(...styles.hexToRgb(hex)), + enumerable: false, + }, + ansi256ToAnsi: { + value(code) { + if (code < 8) { + return 30 + code; + } + + if (code < 16) { + return 90 + (code - 8); + } + + let red; + let green; + let blue; + + if (code >= 232) { + red = (((code - 232) * 10) + 8) / 255; + green = red; + blue = red; + } else { + code -= 16; + + const remainder = code % 36; + + red = Math.floor(code / 36) / 5; + green = Math.floor(remainder / 6) / 5; + blue = (remainder % 6) / 5; + } + + const value = Math.max(red, green, blue) * 2; + + if (value === 0) { + return 30; + } + + // eslint-disable-next-line no-bitwise + let result = 30 + ((Math.round(blue) << 2) | (Math.round(green) << 1) | Math.round(red)); + + if (value === 2) { + result += 60; + } + + return result; + }, + enumerable: false, + }, + rgbToAnsi: { + value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)), + enumerable: false, + }, + hexToAnsi: { + value: hex => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)), + enumerable: false, + }, + }); + + return styles; +} + +const ansiStyles = assembleStyles(); + +export default ansiStyles; diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/browser.d.ts b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/browser.d.ts new file mode 100644 index 000000000..0e8a9ccb8 --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/browser.d.ts @@ -0,0 +1 @@ +export {default} from './index.js'; diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/browser.js b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/browser.js new file mode 100644 index 000000000..fbb6ce0fc --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/browser.js @@ -0,0 +1,34 @@ +/* eslint-env browser */ + +const level = (() => { + if (!('navigator' in globalThis)) { + return 0; + } + + if (globalThis.navigator.userAgentData) { + const brand = navigator.userAgentData.brands.find(({brand}) => brand === 'Chromium'); + if (brand && brand.version > 93) { + return 3; + } + } + + if (/\b(Chrome|Chromium)\//.test(globalThis.navigator.userAgent)) { + return 1; + } + + return 0; +})(); + +const colorSupport = level !== 0 && { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3, +}; + +const supportsColor = { + stdout: colorSupport, + stderr: colorSupport, +}; + +export default supportsColor; diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/index.d.ts b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/index.d.ts new file mode 100644 index 000000000..db44a7851 --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/index.d.ts @@ -0,0 +1,55 @@ +import type {WriteStream} from 'node:tty'; + +export type Options = { + /** + Whether `process.argv` should be sniffed for `--color` and `--no-color` flags. + + @default true + */ + readonly sniffFlags?: boolean; +}; + +/** +Levels: +- `0` - All colors disabled. +- `1` - Basic 16 colors support. +- `2` - ANSI 256 colors support. +- `3` - Truecolor 16 million colors support. +*/ +export type ColorSupportLevel = 0 | 1 | 2 | 3; + +/** +Detect whether the terminal supports color. +*/ +export type ColorSupport = { + /** + The color level. + */ + level: ColorSupportLevel; + + /** + Whether basic 16 colors are supported. + */ + hasBasic: boolean; + + /** + Whether ANSI 256 colors are supported. + */ + has256: boolean; + + /** + Whether Truecolor 16 million colors are supported. + */ + has16m: boolean; +}; + +export type ColorInfo = ColorSupport | false; + +export function createSupportsColor(stream?: WriteStream, options?: Options): ColorInfo; + +declare const supportsColor: { + stdout: ColorInfo; + stderr: ColorInfo; +}; + +export default supportsColor; diff --git a/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/index.js b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/index.js new file mode 100644 index 000000000..265d7f858 --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/node_modules/chalk/source/vendor/supports-color/index.js @@ -0,0 +1,190 @@ +import process from 'node:process'; +import os from 'node:os'; +import tty from 'node:tty'; + +// From: https://github.com/sindresorhus/has-flag/blob/main/index.js +/// function hasFlag(flag, argv = globalThis.Deno?.args ?? process.argv) { +function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process.argv) { + const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); + const position = argv.indexOf(prefix + flag); + const terminatorPosition = argv.indexOf('--'); + return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); +} + +const {env} = process; + +let flagForceColor; +if ( + hasFlag('no-color') + || hasFlag('no-colors') + || hasFlag('color=false') + || hasFlag('color=never') +) { + flagForceColor = 0; +} else if ( + hasFlag('color') + || hasFlag('colors') + || hasFlag('color=true') + || hasFlag('color=always') +) { + flagForceColor = 1; +} + +function envForceColor() { + if ('FORCE_COLOR' in env) { + if (env.FORCE_COLOR === 'true') { + return 1; + } + + if (env.FORCE_COLOR === 'false') { + return 0; + } + + return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3); + } +} + +function translateLevel(level) { + if (level === 0) { + return false; + } + + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3, + }; +} + +function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) { + const noFlagForceColor = envForceColor(); + if (noFlagForceColor !== undefined) { + flagForceColor = noFlagForceColor; + } + + const forceColor = sniffFlags ? flagForceColor : noFlagForceColor; + + if (forceColor === 0) { + return 0; + } + + if (sniffFlags) { + if (hasFlag('color=16m') + || hasFlag('color=full') + || hasFlag('color=truecolor')) { + return 3; + } + + if (hasFlag('color=256')) { + return 2; + } + } + + // Check for Azure DevOps pipelines. + // Has to be above the `!streamIsTTY` check. + if ('TF_BUILD' in env && 'AGENT_NAME' in env) { + return 1; + } + + if (haveStream && !streamIsTTY && forceColor === undefined) { + return 0; + } + + const min = forceColor || 0; + + if (env.TERM === 'dumb') { + return min; + } + + if (process.platform === 'win32') { + // Windows 10 build 10586 is the first Windows release that supports 256 colors. + // Windows 10 build 14931 is the first release that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(osRelease[0]) >= 10 + && Number(osRelease[2]) >= 10_586 + ) { + return Number(osRelease[2]) >= 14_931 ? 3 : 2; + } + + return 1; + } + + if ('CI' in env) { + if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => key in env)) { + return 3; + } + + if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } + + return min; + } + + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } + + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if (env.TERM === 'xterm-kitty') { + return 3; + } + + if (env.TERM === 'xterm-ghostty') { + return 3; + } + + if (env.TERM === 'wezterm') { + return 3; + } + + if ('TERM_PROGRAM' in env) { + const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': { + return version >= 3 ? 3 : 2; + } + + case 'Apple_Terminal': { + return 2; + } + // No default + } + } + + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + + if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + + if ('COLORTERM' in env) { + return 1; + } + + return min; +} + +export function createSupportsColor(stream, options = {}) { + const level = _supportsColor(stream, { + streamIsTTY: stream && stream.isTTY, + ...options, + }); + + return translateLevel(level); +} + +const supportsColor = { + stdout: createSupportsColor({isTTY: tty.isatty(1)}), + stderr: createSupportsColor({isTTY: tty.isatty(2)}), +}; + +export default supportsColor; diff --git a/tests/fixtures/npm/cases/chalk5/package.json b/tests/fixtures/npm/cases/chalk5/package.json new file mode 100644 index 000000000..3804daa8d --- /dev/null +++ b/tests/fixtures/npm/cases/chalk5/package.json @@ -0,0 +1 @@ +{ "name": "chalk5-case", "private": true, "type": "module" } diff --git a/tests/harness/__snapshots__/builtin-class-alias-escape.ts.txt b/tests/harness/__snapshots__/builtin-class-alias-escape.ts.txt new file mode 100644 index 000000000..9d1d981e2 --- /dev/null +++ b/tests/harness/__snapshots__/builtin-class-alias-escape.ts.txt @@ -0,0 +1,22 @@ +builtin-class-alias-escape.ts:7:16 - error SC1090: a builtin class alias as an escaping value (use 'Emitter' with new, instanceof, typeof or .name) is not supported yet + + 6 | const Emitter = EventEmitter; + 7 | const kinds = [Emitter]; + | ^~~~~~~ + 8 | console.log(kinds.length); + +builtin-class-alias-escape.ts:11:24 - error SC1090: a builtin class alias as an escaping value (use 'M' with new, instanceof, typeof or .name) is not supported yet + + 10 | const M = Map; + 11 | const boxed: unknown = M; + | ^ + 12 | console.log(boxed !== undefined); + +builtin-class-alias-escape.ts:14:15 - error SC2020: 'events.EventEmitter' is part of the standard library types but has no scriptc lowering yet + + 13 | + 14 | let Mutable = EventEmitter; + | ^~~~~~~~~~~~ + 15 | console.log(typeof Mutable); + + 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__/class-values.ts.txt b/tests/harness/__snapshots__/class-values.ts.txt index 9d5a43c1f..953d1c2d1 100644 --- a/tests/harness/__snapshots__/class-values.ts.txt +++ b/tests/harness/__snapshots__/class-values.ts.txt @@ -1,52 +1,52 @@ -class-values.ts:22:7 - error SC2020: 'ErrorConstructor' is part of the standard library types but has no scriptc lowering yet +class-values.ts:24:7 - error SC2009: values of type 'ErrorConstructor[]' cannot be compiled: the array shape is supported, but its element type 'ErrorConstructor' does not compile - 21 | // Builtin classes as values: their construction is libCall-shaped. - 22 | const E = Error; - | ^ - 23 | + 23 | const E = Error; + 24 | const errorClasses = [E]; + | ^~~~~~~~~~~~ + 25 | - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) + 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') -class-values.ts:22:11 - error SC2020: 'Error' is part of the standard library types but has no scriptc lowering yet +class-values.ts:24:22 - error SC2009: values of type 'ErrorConstructor[]' cannot be compiled: the array shape is supported, but its element type 'ErrorConstructor' does not compile - 21 | // Builtin classes as values: their construction is libCall-shaped. - 22 | const E = Error; - | ^~~~~ - 23 | + 23 | const E = Error; + 24 | const errorClasses = [E]; + | ^~~ + 25 | - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) + 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') -class-values.ts:44:1 - error SC1090: assigning the static 'tag' through a class value (JS creates an OWN property on the runtime class — assign through the declaring class's name, which subclasses here) is not supported yet +class-values.ts:46:1 - error SC1090: assigning the static 'tag' through a class value (JS creates an OWN property on the runtime class — assign through the declaring class's name, which subclasses here) is not supported yet - 43 | let someClass: typeof Base = Base; - 44 | someClass.tag = "write"; + 45 | let someClass: typeof Base = Base; + 46 | someClass.tag = "write"; | ^~~~~~~~~~~~~ - 45 | + 47 | -class-values.ts:48:13 - error SC1090: reading the static member 'tag' through a class value (a subclass of 'Base' redeclares it, so the runtime class decides which declaration answers) is not supported yet +class-values.ts:50:13 - error SC1090: reading the static member 'tag' through a class value (a subclass of 'Base' redeclares it, so the runtime class decides which declaration answers) is not supported yet - 47 | // runtime class decides which declaration answers. - 48 | console.log(someClass.tag); + 49 | // runtime class decides which declaration answers. + 50 | console.log(someClass.tag); | ^~~~~~~~~~~~~ - 49 | + 51 | -class-values.ts:52:10 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet +class-values.ts:54:10 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet - 51 | function make(): unknown { - 52 | return class { static value = 1; }; + 53 | function make(): unknown { + 54 | return class { static value = 1; }; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ - 53 | } + 55 | } -class-values.ts:59:18 - error SC1090: class expressions with static initializers or static blocks outside a whole-initializer position (their declaration-time code must run exactly where the expression evaluates — bind the class in its own top-level `const C = class …` statement) is not supported yet +class-values.ts:61:18 - error SC1090: class expressions with static initializers or static blocks outside a whole-initializer position (their declaration-time code must run exactly where the expression evaluates — bind the class in its own top-level `const C = class …` statement) is not supported yet - 58 | // evaluates. - 59 | const pair = [1, class WithStatic { static x = 1; }] as const; + 60 | // evaluates. + 61 | const pair = [1, class WithStatic { static x = 1; }] as const; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 60 | + 62 | -class-values.ts:79:56 - error SC1090: receiver-dependent static methods through a polymorphic class value is not supported yet +class-values.ts:81:56 - error SC1090: receiver-dependent static methods through a polymorphic class value is not supported yet - 78 | class OtherThis extends UsesThis {} - 79 | function self(value: typeof UsesThis): string { return value.self(); } + 80 | class OtherThis extends UsesThis {} + 81 | function self(value: typeof UsesThis): string { return value.self(); } | ^~~~~~~~~~ - 80 | console.log(self(OtherThis)); \ No newline at end of file + 82 | console.log(self(OtherThis)); \ No newline at end of file diff --git a/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt b/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt index b6996b3c1..87470b621 100644 --- a/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt +++ b/tests/harness/__snapshots__/collection-iterator-helpers.ts.txt @@ -1,35 +1,17 @@ -collection-iterator-helpers.ts:5:1 - error SC2020: 'collection iterator .map()' is part of the standard library types but has no scriptc lowering yet +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 - 4 | const values = map.values(); - 5 | values.map(value => value + 1); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 6 | map.keys().toArray(); + 4 | // flatMap callbacks return arrays or generators. + 5 | set.values().flatMap((value) => new Set([value, value + 1])); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 6 | // return() on a helper object. - hint: stored Map/Set iterators support .next() and iteration; iterator helper methods have no lowering + hint: callbacks returning an array or a generator are the supported forms -collection-iterator-helpers.ts:6:1 - error SC2020: 'collection iterator .toArray()' is part of the standard library types but has no scriptc lowering yet +collection-iterator-helpers.ts:8:1 - error SC2020: 'IteratorObject.return' is part of the standard library types but has no scriptc lowering yet - 5 | values.map(value => value + 1); - 6 | map.keys().toArray(); - | ^~~~~~~~~~~~~~~~~~~~ - 7 | const set = new Set(); + 7 | const mapped = set.values().map((value) => value * 2); + 8 | mapped.return!(); + | ^~~~~~~~~~~~~ + 9 | // Each helper call must produce a diagnostic. - hint: stored Map/Set iterators support .next() and iteration; iterator helper methods have no lowering - -collection-iterator-helpers.ts:8:1 - error SC2020: 'collection iterator .filter()' is part of the standard library types but has no scriptc lowering yet - - 7 | const set = new Set(); - 8 | set.values().filter(value => value > 0); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 9 | set.entries().take(1); - - hint: stored Map/Set iterators support .next() and iteration; iterator helper methods have no lowering - -collection-iterator-helpers.ts:9:1 - error SC2020: 'collection iterator .take()' is part of the standard library types but has no scriptc lowering yet - - 8 | set.values().filter(value => value > 0); - 9 | set.entries().take(1); - | ^~~~~~~~~~~~~~~~~~~~~ - 10 | // Each helper call must produce a diagnostic. - - hint: stored Map/Set iterators support .next() and iteration; iterator helper methods have no lowering \ No newline at end of file + 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__/coverage-divergences.txt b/tests/harness/__snapshots__/coverage-divergences.txt index 67c9b57e3..2f811f892 100644 --- a/tests/harness/__snapshots__/coverage-divergences.txt +++ b/tests/harness/__snapshots__/coverage-divergences.txt @@ -2,6 +2,7 @@ scriptc coverage tests/coverage-fixtures/divergences.ts statements analyzed 18 compile statically 18 (100%) + in dynamic tier 1 (operate on values typed only at runtime; --explain-dynamic lists them) fully static — all analyzed statements compile to native code. diff --git a/tests/harness/__snapshots__/coverage-dynamic-mix.txt b/tests/harness/__snapshots__/coverage-dynamic-mix.txt index a3e714bde..2ad84f0af 100644 --- a/tests/harness/__snapshots__/coverage-dynamic-mix.txt +++ b/tests/harness/__snapshots__/coverage-dynamic-mix.txt @@ -2,6 +2,7 @@ scriptc coverage tests/coverage-fixtures/dynamic-mix.ts statements analyzed 8 compile statically 7 (87%) + in dynamic tier 2 (operate on values typed only at runtime; --explain-dynamic lists them) blockers: ×1 SC2012 '.toPrecision()' on numbers cannot compile statically diff --git a/tests/harness/__snapshots__/coverage-explain-dynamic.txt b/tests/harness/__snapshots__/coverage-explain-dynamic.txt new file mode 100644 index 000000000..1c86a808c --- /dev/null +++ b/tests/harness/__snapshots__/coverage-explain-dynamic.txt @@ -0,0 +1,16 @@ + dynamic tier (tier assignment, report-only; --tier=auto) + 4 functions already operate on dynamic values (1 statement) + readName tests/coverage-fixtures/tier-demotions.ts:15:1: %dyn.error.stringConstructor at tests/coverage-fixtures/tier-demotions.ts:17:10 + %dyn.error.stringConstructor.0 tests/coverage-fixtures/tier-demotions.ts:17:10: dynCall at tests/coverage-fixtures/tier-demotions.ts:17:10 + %dyn.class.property.0 tests/coverage-fixtures/tier-demotions.ts:17:17: %dyn.class.readPropertiesOrEmpty at tests/coverage-fixtures/tier-demotions.ts:17:17 + %method.value:%%Error.toString tests/coverage-fixtures/tier-demotions.ts:17:10: dyn.this at tests/coverage-fixtures/tier-demotions.ts:17:10 + + 2 demotion requests (refusals and fences the dynamic tier would replace) + class Point tests/coverage-fixtures/tier-demotions.ts:2:1 (1 request) + <- SC1090 reflection at tests/coverage-fixtures/tier-demotions.ts:10:1: assignment to non-variables are not supported yet + -> function turns dynamic + function tests/coverage-fixtures/tier-demotions.ts:1:1 (1 site inside) + function precise tests/coverage-fixtures/tier-demotions.ts:21:1 (1 site inside) + binding n in precise tests/coverage-fixtures/tier-demotions.ts:21:18 (1 request) + <- SC2012 operation at tests/coverage-fixtures/tier-demotions.ts:22:10: '.toPrecision()' on numbers cannot compile statically + -> function precise turns dynamic \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-js-gap.txt b/tests/harness/__snapshots__/coverage-js-gap.txt index 3150216b6..3b6fa678c 100644 --- a/tests/harness/__snapshots__/coverage-js-gap.txt +++ b/tests/harness/__snapshots__/coverage-js-gap.txt @@ -2,5 +2,6 @@ scriptc coverage tests/coverage-fixtures/js-gap/gap.js statements analyzed 5 compile statically 5 (100%) + in dynamic tier 3 (operate on values typed only at runtime; --explain-dynamic lists them) fully static — all analyzed statements compile to native code. \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-mixed.txt b/tests/harness/__snapshots__/coverage-mixed.txt index 88b61c39b..c3b348f9c 100644 --- a/tests/harness/__snapshots__/coverage-mixed.txt +++ b/tests/harness/__snapshots__/coverage-mixed.txt @@ -2,6 +2,7 @@ scriptc coverage tests/coverage-fixtures/mixed.ts statements analyzed 13 compile statically 12 (92%) + in dynamic tier 1 (operate on values typed only at runtime; --explain-dynamic lists them) blockers: ×1 SC1031 object destructuring of non-record values (the source is array-typed) diff --git a/tests/harness/__snapshots__/coverage-npm-packages.txt b/tests/harness/__snapshots__/coverage-npm-packages.txt index 0ba05286a..475b10e45 100644 --- a/tests/harness/__snapshots__/coverage-npm-packages.txt +++ b/tests/harness/__snapshots__/coverage-npm-packages.txt @@ -2,6 +2,7 @@ scriptc coverage tests/corpus/native-npm-imports/main.ts statements analyzed 14 compile statically 14 (100%) + in dynamic tier 3 (operate on values typed only at runtime; --explain-dynamic lists them) npm package compilation: mathkit static diff --git a/tests/harness/__snapshots__/coverage-tier-demotions.txt b/tests/harness/__snapshots__/coverage-tier-demotions.txt new file mode 100644 index 000000000..7b52f9d63 --- /dev/null +++ b/tests/harness/__snapshots__/coverage-tier-demotions.txt @@ -0,0 +1,13 @@ +scriptc coverage tests/coverage-fixtures/tier-demotions.ts + + statements analyzed 5 + compile statically 3 (60%) + in dynamic tier 1 (operate on values typed only at runtime; --explain-dynamic lists them) + + blockers: + ×1 SC1090 assignment to non-variables + at tests/coverage-fixtures/tier-demotions.ts:10:1 + hint: this form is valid TypeScript but does not compile yet — rewrite it with a supported equivalent of the construct the message names (https://scriptc.dev/limitations lists the boundary) + ×1 SC2012 '.toPrecision()' on numbers cannot compile statically + at tests/coverage-fixtures/tier-demotions.ts:22:10 + hint: use a supported API or rewrite this operation for native compilation \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-wasm.txt b/tests/harness/__snapshots__/coverage-wasm.txt new file mode 100644 index 000000000..5b5bea36a --- /dev/null +++ b/tests/harness/__snapshots__/coverage-wasm.txt @@ -0,0 +1,31 @@ +scriptc coverage tests/coverage-fixtures/wasm/main.ts + + statements analyzed 8 + compile statically 6 (75%) + 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) + inline module 39 bytes valid + 1 function, 0 imports, 1 export + found: inline at tests/coverage-fixtures/wasm/glue.js:5 + 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/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 + tests/coverage-fixtures/wasm/build/generated.wasm 43 bytes uses simd + 1 function, 0 imports, 0 exports + 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 diff --git a/tests/harness/__snapshots__/coverage-width-copies.txt b/tests/harness/__snapshots__/coverage-width-copies.txt index dd99d4bb5..46e8e949d 100644 --- a/tests/harness/__snapshots__/coverage-width-copies.txt +++ b/tests/harness/__snapshots__/coverage-width-copies.txt @@ -2,6 +2,7 @@ scriptc coverage tests/coverage-fixtures/width-copies.ts statements analyzed 14 compile statically 14 (100%) + in dynamic tier 1 (operate on values typed only at runtime; --explain-dynamic lists them) fully static — all analyzed statements compile to native code. diff --git a/tests/harness/__snapshots__/es2025-fences.ts.txt b/tests/harness/__snapshots__/es2025-fences.ts.txt index e2dc5f40f..d7eda27b6 100644 --- a/tests/harness/__snapshots__/es2025-fences.ts.txt +++ b/tests/harness/__snapshots__/es2025-fences.ts.txt @@ -43,15 +43,6 @@ es2025-fences.ts:22:16 - error SC2020: 'Atomics.waitAsync' is part of the standa hint: use integer load, store, exchange, compareExchange, add, sub, and, or, xor, wait or notify -es2025-fences.ts:25:7 - error SC2020: 'IteratorObject' 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()); - | ^~ - 26 | - - hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) - 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. @@ -59,7 +50,7 @@ es2025-fences.ts:25:12 - error SC2020: 'Iterator.from' is part of the standard l | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 26 | - hint: first-class iterator objects have no lowering — iterator helpers compile as one chain on an array iterator, consumed in place: arr.values().map(f).take(n).toArray() + hint: iterator helpers apply directly to generators and to array, Map and Set iterators: arr.values().map(f).take(n).toArray() 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__/json-class-fields.ts.txt b/tests/harness/__snapshots__/json-class-fields.ts.txt new file mode 100644 index 000000000..5e6c30fdf --- /dev/null +++ b/tests/harness/__snapshots__/json-class-fields.ts.txt @@ -0,0 +1,20 @@ +json-class-fields.ts:20:28 - error SC1090: JSON.stringify of 'Pattern' values (field 'source' of class 'Pattern' has type 'RegExp', which the runtime JSON traversal cannot write) are not supported yet + + 19 | + 20 | console.log(JSON.stringify(new Pattern())); + | ^~~~~~~~~~~~~ + 21 | console.log(JSON.stringify(new Job())); + +json-class-fields.ts:21:28 - error SC1090: JSON.stringify of 'Job' values (field 'pending' of class 'Job' has type 'Promise', which the runtime JSON traversal cannot write) are not supported yet + + 20 | console.log(JSON.stringify(new Pattern())); + 21 | console.log(JSON.stringify(new Job())); + | ^~~~~~~~~ + 22 | console.log(JSON.stringify({ catalog: new Catalog() })); + +json-class-fields.ts:22:28 - error SC1090: JSON.stringify of '{ catalog: Catalog }' values (field 'source' of class 'Pattern' has type 'RegExp', which the runtime JSON traversal cannot write) are not supported yet + + 21 | console.log(JSON.stringify(new Job())); + 22 | console.log(JSON.stringify({ catalog: new Catalog() })); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~ + 23 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/local-class-boundaries.ts.txt b/tests/harness/__snapshots__/local-class-boundaries.ts.txt index e3e27ad95..04dbc336c 100644 --- a/tests/harness/__snapshots__/local-class-boundaries.ts.txt +++ b/tests/harness/__snapshots__/local-class-boundaries.ts.txt @@ -16,11 +16,4 @@ local-class-boundaries.ts:5:29 - error SC1090: function-local classes with stati 4 | function derived() { return class extends Base {}; } 5 | function generic() { return class { read(value: T): T { return value; } }; } | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 6 | privateBrand(); - -local-class-boundaries.ts:13:1 - error SC2001: values of type 'typeof Stored' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') - - 12 | } - 13 | genericFactory(7); - | ^~~~~~~~~~~~~~~~~~~~~~~~~ - 14 | // End of unsupported local-class forms. \ No newline at end of file + 6 | privateBrand(); \ No newline at end of file diff --git a/tests/harness/__snapshots__/local-class-type-query-brand.ts.txt b/tests/harness/__snapshots__/local-class-type-query-brand.ts.txt new file mode 100644 index 000000000..6db3418a8 --- /dev/null +++ b/tests/harness/__snapshots__/local-class-type-query-brand.ts.txt @@ -0,0 +1,6 @@ +local-class-type-query-brand.ts:4:3 - error SC1090: function-local classes with private brands are not supported yet + + 3 | function make() { + 4 | class Box { + | ^~~~~~~~~~~ + 5 | #v = 1; \ No newline at end of file diff --git a/tests/harness/__snapshots__/local-class-type-query-js.js.txt b/tests/harness/__snapshots__/local-class-type-query-js.js.txt new file mode 100644 index 000000000..60422a5dc --- /dev/null +++ b/tests/harness/__snapshots__/local-class-type-query-js.js.txt @@ -0,0 +1,6 @@ +local-class-type-query-js.js:14:9 - error SC1090: fields assigned outside the constructor's top level ('this.missingFilesMap' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor) is not supported yet + + 13 | this.missingFilesMap.clear(); + 14 | this.missingFilesMap = void 0; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 15 | } \ No newline at end of file diff --git a/tests/harness/__snapshots__/local-class-type-query.ts.txt b/tests/harness/__snapshots__/local-class-type-query.ts.txt new file mode 100644 index 000000000..87489fcd5 --- /dev/null +++ b/tests/harness/__snapshots__/local-class-type-query.ts.txt @@ -0,0 +1,6 @@ +local-class-type-query.ts:6:3 - error SC1090: function-local classes with statics, decorators, or generic members are not supported yet + + 5 | function make() { + 6 | class Box { + | ^~~~~~~~~~~ + 7 | static count = 0; \ No newline at end of file diff --git a/tests/harness/__snapshots__/overloaded-members.ts.txt b/tests/harness/__snapshots__/overloaded-members.ts.txt new file mode 100644 index 000000000..17e608785 --- /dev/null +++ b/tests/harness/__snapshots__/overloaded-members.ts.txt @@ -0,0 +1,35 @@ +overloaded-members.ts:15:7 - error SC2007: values of type '{ (event: "a", handler: (x: number) => void): void; (event: "b", handler: (x: string) => void): void; }' cannot be compiled: the type declares multiple call signatures (overloads), and a compiled function value is always one concrete signature + + 14 | api.on("a", (x) => console.log(x + 1)); + 15 | const detached = api.on; + | ^~~~~~~~ + 16 | console.log(typeof detached); + + hint: annotate the slot with the ONE signature the program actually calls (e.g. '(x: number) => string'), or wrap the overloaded function in a single-signature arrow + +overloaded-members.ts:16:20 - error SC2004: uses of 'detached' inherit the blocker on its declaration + + 15 | const detached = api.on; + 16 | console.log(typeof detached); + | ^~~~~~~~ + 17 | + + hint: the declaration of 'detached' did not compile — fix the diagnostic reported there and these sites clear with it + +overloaded-members.ts:22:15 - error SC2009: values of type 'Emitter' cannot be compiled: the record shape is supported, but its member 'emit' is an overload set, and its overload 'emit(event: "many", ...values: number[]): void' has a rest parameter (an overload set stores one fixed-arity function) + + 21 | } + 22 | function fire(e: Emitter): void { + | ^ + 23 | e.emit("many", 1, 2); + + 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') + +overloaded-members.ts:25:20 - error SC2004: uses of 'fire' inherit the blocker on its declaration + + 24 | } + 25 | console.log(typeof fire); + | ^~~~ + 26 | + + hint: the declaration of 'fire' 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__/speculative-function-refusal.ts.txt b/tests/harness/__snapshots__/speculative-function-refusal.ts.txt new file mode 100644 index 000000000..c8b0cd28c --- /dev/null +++ b/tests/harness/__snapshots__/speculative-function-refusal.ts.txt @@ -0,0 +1,17 @@ +speculative-function-refusal.ts:6:15 - error SC2020: 'eval' is part of the standard library types but has no scriptc lowering yet + + 5 | console.log("wrapper"); + 6 | console.log(eval("1 + 1")); + | ^~~~ + 7 | }).apply(null, []); + + hint: runtime code evaluation cannot be compiled ahead of time + +speculative-function-refusal.ts:11:15 - error SC2020: 'new Function' is part of the standard library types but has no scriptc lowering yet + + 10 | class Derived extends (function () { + 11 | console.log(new Function("return 1")()); + | ^~~~~~~~~~~~~~~~~~~~~~~~ + 12 | return Base; + + hint: runtime code generation cannot be compiled ahead of time (the eval stance) — write the function \ No newline at end of file diff --git a/tests/harness/__snapshots__/string-iterator-helpers.ts.txt b/tests/harness/__snapshots__/string-iterator-helpers.ts.txt index 4d9d1ad8d..ef768934e 100644 --- a/tests/harness/__snapshots__/string-iterator-helpers.ts.txt +++ b/tests/harness/__snapshots__/string-iterator-helpers.ts.txt @@ -1,12 +1,3 @@ -string-iterator-helpers.ts:3:7 - error SC2020: 'IteratorObject' 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: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) - 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. @@ -14,4 +5,4 @@ string-iterator-helpers.ts:3:15 - error SC2020: 'StringIterator.map' is part of | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 | - hint: iterator helpers lower on chains from arr.values() or arr.entries() that end in a terminal (toArray, forEach, reduce, some, every, find) — spread the string first: [...s].values() \ No newline at end of file + 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/__snapshots__/webassembly-fences.ts.txt b/tests/harness/__snapshots__/webassembly-fences.ts.txt new file mode 100644 index 000000000..da38a2c57 --- /dev/null +++ b/tests/harness/__snapshots__/webassembly-fences.ts.txt @@ -0,0 +1,98 @@ +webassembly-fences.ts:10:15 - error SC2020: 'WebAssembly.validate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet + + 9 | async function load(options: LoaderOptions, bytes: Uint8Array): Promise { + 10 | console.log(WebAssembly.validate(bytes)); + | ^~~~~~~~~~~~~~~~~~~~ + 11 | const module = await WebAssembly.compile(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:11:24 - error SC2020: 'WebAssembly.compile' is not supported yet: scriptc does not compile WebAssembly modules to native code yet + + 10 | console.log(WebAssembly.validate(bytes)); + 11 | const module = await WebAssembly.compile(bytes); + | ^~~~~~~~~~~~~~~~~~~ + 12 | const { instance } = await WebAssembly.instantiate(bytes, options.imports); + + 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:12:30 - error SC2020: 'WebAssembly.instantiate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet + + 11 | const module = await WebAssembly.compile(bytes); + 12 | const { instance } = await WebAssembly.instantiate(bytes, options.imports); + | ^~~~~~~~~~~~~~~~~~~~~~~ + 13 | console.log(instance instanceof WebAssembly.Instance, module); + + 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:13:35 - error SC2020: 'WebAssembly.Instance' is not supported yet: scriptc does not compile WebAssembly modules to native code yet + + 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); + | ^~~~~~~~~~~~~~~~~~~~~~~~ + 18 | const api = globalThis.WebAssembly; + + 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 + + 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); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~ + 21 | } + + 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:27:33 - error SC2020: 'globalThis.WebAssembly' is not supported yet: scriptc does not compile WebAssembly modules to native code yet + + 26 | } + 27 | const { WebAssembly: viewed } = globalThis as unknown as { WebAssembly: WebAssemblyGlobal }; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 28 | console.log(typeof viewed.compile); + + 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 diff --git a/tests/harness/__snapshots__/zlib-class-alias-escape.ts.txt b/tests/harness/__snapshots__/zlib-class-alias-escape.ts.txt index ea9d76089..703038025 100644 --- a/tests/harness/__snapshots__/zlib-class-alias-escape.ts.txt +++ b/tests/harness/__snapshots__/zlib-class-alias-escape.ts.txt @@ -1,4 +1,4 @@ -zlib-class-alias-escape.ts:7:18 - error SC1090: a zlib class alias as an escaping value (use 'Gz' with new, instanceof, typeof or .name) is not supported yet +zlib-class-alias-escape.ts:7:18 - error SC1090: a builtin class alias as an escaping value (use 'Gz' with new, instanceof, typeof or .name) is not supported yet 6 | const Gz = zlib.Gzip; 7 | const classes = [Gz]; diff --git a/tests/harness/chalk.test.ts b/tests/harness/chalk.test.ts new file mode 100644 index 000000000..4d99bf3ee --- /dev/null +++ b/tests/harness/chalk.test.ts @@ -0,0 +1,97 @@ +/* chalk 5 compiled from its published JavaScript (tests/fixtures/npm/cases/ + * chalk5): the program runs under Node and as a native binary with each + * color-detection input supports-color reads — FORCE_COLOR values and the + * --color/--no-color flags — and stdout, stderr and the exit status must + * match byte for byte. npm.test.ts runs the same case once in the + * harness's own environment. */ +import { execFile } from "node:child_process"; +import { mkdirSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { promisify } from "node:util"; +import { beforeAll, describe, expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const entry = join(repoRoot, "tests/fixtures/npm/cases/chalk5/main.ts"); +const outDir = join(repoRoot, "node_modules/.cache/scriptc-tests/chalk5"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +interface RunResult { + stdout: string; + stderr: string; + exitCode: number; +} + +async function run(cmd: string, args: string[], env: NodeJS.ProcessEnv): Promise { + try { + const pending = execFileAsync(cmd, args, { cwd: dirname(entry), env, encoding: "utf8" }); + pending.child.stdin?.end(); + const { stdout, stderr } = await pending; + return { stdout, stderr, exitCode: 0 }; + } catch (err) { + const e = err as { code?: unknown; stdout?: string; stderr?: string }; + if (typeof e.stdout !== "string" || typeof e.stderr !== "string") throw err; + return { + stdout: e.stdout, + stderr: e.stderr, + exitCode: typeof e.code === "number" ? e.code : -1, + }; + } +} + +/** The harness environment without the color inputs, plus `force`. */ +function colorEnv(force: string | undefined): NodeJS.ProcessEnv { + const env = { ...process.env }; + delete env["FORCE_COLOR"]; + delete env["NO_COLOR"]; + delete env["NODE_DISABLE_COLORS"]; + if (force !== undefined) env["FORCE_COLOR"] = force; + return env; +} + +const cases: { name: string; force: string | undefined; argv: string[] }[] = [ + { name: "FORCE_COLOR unset", force: undefined, argv: [] }, + ...["0", "1", "2", "3", "true", "false", ""].map((force) => ({ + name: `FORCE_COLOR=${JSON.stringify(force)}`, + force, + argv: [], + })), + ...[ + ["--no-color"], + ["--color"], + ["--color=256"], + ["--color=16m"], + ["--color", "--", "--no-color"], + ].map((argv) => ({ name: argv.join(" "), force: undefined, argv })), + { name: "FORCE_COLOR=3 --no-color", force: "3", argv: ["--no-color"] }, +]; + +let binary = ""; + +beforeAll(async () => { + mkdirSync(outDir, { recursive: true }); + const result = await compile(entry, { outPath: join(outDir, "program"), outDir, sanitize }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + binary = result.binaryPath; +}, 300_000); + +describe("chalk 5 color detection matches Node", () => { + for (const c of cases) { + test( + c.name, + async () => { + const env = colorEnv(c.force); + const [node, native] = await Promise.all([ + run(process.execPath, [entry, ...c.argv], env), + run(binary, c.argv, env), + ]); + expect(native.stdout).toBe(node.stdout); + expect(native.stderr).toBe(node.stderr); + expect(native.exitCode).toBe(node.exitCode); + }, + 60_000, + ); + } +}); diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index 9f3c007f3..48b392237 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -1341,6 +1341,8 @@ "4451-int32-field-storage.ts": 3457, "4452-int32-field-dynamic-writes.ts": 3861, "4453-int32-closed-locals.ts": 2280, + "4454-int32-field-reflection-reads.ts": 3951, + "4455-live-view-reads-without-stores.ts": 3598, "4471-unassigned-field-reads.ts": 3309, "4476-string-ascii-header-fact.ts": 2846, "4477-buffer-write-ascii-runs.ts": 2125, @@ -1509,6 +1511,8 @@ "5020-lifetime-region-large.ts": 3840, "5021-lifetime-region-opaque-holders.ts": 2702, "5022-lifetime-region-workers.ts": 3127, + "5023-lifetime-region-await.ts": 3656, + "5024-lifetime-region-await-forms.ts": 3342, "503-array-functions.ts": 2610, "504-array-rc-stress.ts": 2247, "5040-promise-all-race-turns.ts": 3415, @@ -1690,6 +1694,16 @@ "5248-event-target-subclass-warnings.ts": 2743, "5249-event-target-subclass-unconstructed.ts": 1632, "525-map-rc-stress.ts": 3853, + "5250-cipher-aes-gcm.ts": 5771, + "5251-cipher-aes-cbc.ts": 3823, + "5252-cipher-aes-ctr.ts": 2580, + "5253-cipher-chacha20-poly1305.ts": 3418, + "5254-cipher-errors.js": 4743, + "5255-cipher-encodings.cjs": 2250, + "5256-cipher-deprecation.cjs": 3088, + "5257-cipher-vectors.ts": 3572, + "5258-cipher-handles.js": 2980, + "5259-cipher-chunked.ts": 2586, "526-set-basics.ts": 1919, "5260-record-unknown-identity.ts": 2937, "5261-optional-field-identity.ts": 2860, @@ -1729,6 +1743,14 @@ "5319-map-iteration-values.ts": 2689, "532-record-arrays-rc-stress.ts": 2437, "5320-map-iterate-across-compaction.ts": 3526, + "5321-integer-closed-arithmetic-loops.ts": 3349, + "5321-json-parse-cycles.ts": 3308, + "5322-integer-variable-step-loops.ts": 2801, + "5322-json-stringify-parsed-trees.ts": 3139, + "5323-dyn-numeric-index-reads.ts": 2266, + "5323-int32-nonnegative-slot-facts.ts": 2381, + "5324-int32-initializer-locals.ts": 3123, + "5324-structured-clone-documents.ts": 3223, "5325-record-boundary-returns.ts": 1763, "5326-record-boundary-params.ts": 2634, "5327-record-boundary-identity.ts": 2323, @@ -1737,14 +1759,36 @@ "533-array-element-cycles.ts": 2932, "5330-record-boundary-fields.ts": 1977, "5331-record-boundary-methods.ts": 3130, + "5332-int32-narrowed-slot-facts.ts": 2932, + "5333-integer-guarded-while-counters.ts": 2773, "5333-record-boundary-nullable-argument.ts": 3012, "5334-fetch-mentions-without-calls.ts": 2532, + "5335-stream-operators-values.ts": 3448, "5335-top-level-await-pending-exit-code.ts": 1919, + "5336-stream-operators-promise-timing.ts": 3509, "5336-top-level-await-pending-exit-listener.ts": 3734, "5337-dc-subscriber-throw-next-tick.cjs": 2604, + "5337-stream-operators-concurrency.ts": 2478, "5338-js-tdz-function-element.js": 2562, + "5338-stream-operators-checks.cjs": 3991, "5339-array-buffer-constructor.js": 2502, + "5339-stream-for-await-hops.ts": 2919, "534-record-width-subtyping.ts": 2615, + "5340-readable-from-options.ts": 1980, + "5341-compound-any-operand.ts": 3314, + "5341-generator-callback-params.ts": 3955, + "5341-integer-recursion-boundaries.ts": 2534, + "5341-readable-from-dynamic.cjs": 3057, + "5342-compound-any-operand.cjs": 3386, + "5342-generator-scalar-channels.ts": 3184, + "5342-integer-recursion-stack-and-mutual.ts": 3097, + "5342-stream-compose.ts": 2940, + "5343-any-flow-compound.ts": 2063, + "5343-integer-recursion-array-index.ts": 2905, + "5343-iterator-result-records.ts": 3426, + "5343-stream-compose-edges.ts": 3078, + "5344-json-parse-undeclared-keys.ts": 4193, + "5344-stream-pipeline-timing.ts": 3177, "5345-object-values-for-of-unknown.ts": 3008, "5346-object-values-length-dynamic.ts": 3055, "5347-object-values-loop-control.ts": 3030, @@ -1753,6 +1797,12 @@ "535-object-statics.ts": 2312, "5350-object-values-wide-record.ts": 2790, "5351-object-values-methods-index.ts": 3275, + "5352-final-field-loop-reads.ts": 2894, + "5353-captured-const-values.ts": 3727, + "5354-captured-const-tdz.ts": 2243, + "5355-captured-function-declarations.ts": 2917, + "5356-captured-tdz-ordering.ts": 2726, + "5357-init-only-module-bindings.ts": 1956, "536-map-seed-array.ts": 2712, "5360-array-sort-comparator-calls.ts": 3175, "5361-array-sort-inconsistent-comparators.ts": 3161, @@ -1761,6 +1811,16 @@ "5364-array-sort-union-and-boolean-elements.ts": 8514, "5365-array-sort-dynamic-values.js": 2402, "537-map-iter-drains.ts": 2867, + "5370-zlib-stream-roundtrip.ts": 3486, + "5371-zlib-stream-chunking.cjs": 4025, + "5372-zlib-stream-errors.cjs": 2794, + "5373-zlib-stream-options.cjs": 3210, + "5374-zlib-stream-flush-params.ts": 3572, + "5375-zlib-stream-promises.ts": 3156, + "5376-zlib-constants.cjs": 2490, + "5377-zlib-stream-backpressure.cjs": 2917, + "5378-zlib-sync-options.cjs": 2777, + "5379-zlib-callback-options.ts": 2861, "538-map-set-for-of.ts": 2756, "5390-interface-loop-element-lifetimes.ts": 2999, "5391-interface-dispatch-hierarchies.ts": 3168, @@ -1782,7 +1842,18 @@ "5411-inspect-class-hierarchies.ts": 2712, "5412-inspect-dynamic-collections.ts": 2792, "5413-inspect-class-values.cjs": 3020, + "5415-zlib-dynamic-inputs.cjs": 3606, "542-union-element-arrays.ts": 2846, + "5420-readable-from-generators.ts": 3459, + "5421-readable-from-generators.cjs": 3156, + "5422-stream-async-push-wakeup.ts": 3051, + "5423-stream-end-and-eos-timing.ts": 3065, + "5424-stream-write-buffer-encoding.ts": 2687, + "5425-pipeline-generator-stage.ts": 2503, + "5426-pipeline-generator-errors.ts": 3640, + "5427-pipeline-generator-backpressure.ts": 3096, + "5428-pipeline-generator-stages.cjs": 2057, + "5429-pipeline-generator-buffers.ts": 2918, "5430-dyn-number-string-boxes.ts": 3059, "5435-crypto-hash-algorithms.ts": 2030, "5436-crypto-hash-names.js": 2098, @@ -1813,30 +1884,99 @@ "5462-locale-compare-probe-sweep.ts": 3400, "5463-locale-compare-combining-letters.ts": 2733, "5464-locale-compare-fractions.ts": 2833, + "5465-dyn-index-read-arrays.ts": 3603, + "5466-dyn-index-read-special.ts": 2823, "5470-strip-vt-osc.cjs": 2497, "5471-strip-vt-csi.cjs": 2651, "5472-strip-vt-random.cjs": 2375, "5473-strip-vt-unterminated.cjs": 3521, "5474-strip-vt-surrogates.ts": 2534, + "5510-assert-error-properties.js": 2744, + "5511-assert-error-class.ts": 3998, + "5512-assert-error-options.js": 3572, + "5513-assert-deep-equal-loose.js": 3537, + "5514-assert-deep-equal-typed.ts": 2922, + "5515-assert-deep-strict-typed-messages.ts": 2776, + "5516-assert-deep-equal-edges.js": 3465, + "5517-assert-partial-deep-equal.js": 4023, + "5518-assert-partial-deep-typed.ts": 3538, + "5519-assert-partial-deep-typed-edges.ts": 3741, + "5520-assert-record-literal-failures.ts": 3435, + "5520-dyn-property-read-receivers.ts": 3114, + "5521-assert-empty-message.js": 3945, + "5521-dyn-property-read-keys.ts": 2472, + "5522-assert-throws-failure-properties.ts": 3505, + "5523-assert-class-options.js": 3797, + "5524-assert-class-skip-prototype.ts": 2105, + "5530-buffer-pool.ts": 3788, + "5531-buffer-pool-first-slab.ts": 5451, + "5532-buffer-pool-first-slab-untyped.mjs": 37592, "5540-fs-callback-forms.cjs": 3233, "5541-fs-stats-fields.cjs": 3564, "5542-fs-promises-parity.cjs": 3940, "5543-fs-promises-typed.ts": 1655, "5544-fs-opendir.cjs": 3024, "5545-fs-options-encoding.cjs": 2994, + "5546-fs-read-stream.cjs": 3808, + "5547-fs-write-stream.cjs": 5798, "5548-fs-callback-arguments.cjs": 4041, "5549-fs-callback-option-checks.cjs": 3738, "5550-locale-compare-ascii-words.ts": 2509, "5551-array-search-references.ts": 3475, "5552-string-appends.ts": 2703, "5553-string-append-char-code.ts": 2870, + "5554-fs-streams-typed.ts": 4016, + "5555-fs-callback-turns.cjs": 3136, + "5555-http-server-close-keep-alive.ts": 6777, + "5556-http-client-agent-connection.ts": 3427, + "5557-http-outgoing-header-values.ts": 2893, + "5558-http-incoming-duplicate-headers.ts": 3230, + "5559-http-expect-header.ts": 3517, + "5560-http-response-framing.ts": 5004, + "5561-http-max-header-size.ts": 3610, + "5562-https-verify-errors.ts": 3646, + "5563-http-keep-alive-timeout.ts": 11895, + "5564-http-request-signal.ts": 4124, "5565-fs-dirent-untyped.mjs": 2600, "5566-fs-dirent-dyn-boundaries/main.mjs": 2607, + "5567-fs-cpsync.ts": 3736, + "5568-fs-cp-errors.js": 4246, + "5569-fs-cp-symlinks.ts": 2136, + "5570-fs-cp-async.ts": 3348, + "5571-fs-cp-deep.ts": 4817, "5585-record-results-inlined.ts": 2922, "5595-net-ref-unref.ts": 2593, + "5600-dyn-cycle-self.ts": 2318, + "5601-dyn-cycle-mutual.ts": 2455, + "5602-dyn-cycle-arrays.ts": 3115, + "5603-dyn-cycle-identity.ts": 3197, + "5604-dyn-cycle-checks.ts": 3210, + "5605-dyn-cycle-containers.ts": 2935, + "5606-dyn-cycle-capsules.ts": 2308, + "5610-readable-from-sets-and-views.ts": 3154, + "5611-readable-from-class-iterables.ts": 3984, + "5612-readable-from-iterables.cjs": 3740, + "5613-readable-from-iterable-timing.cjs": 4549, + "5614-readable-from-iterable-errors.cjs": 4240, + "5615-next-tick-callback-phases.ts": 3053, + "5616-next-tick-callback-phases.cjs": 3322, + "5617-pipeline-function-ends.ts": 2710, + "5618-pipeline-function-ends-errors.ts": 3693, + "5619-pipeline-function-ends.cjs": 3677, + "5620-timers-promises-values.ts": 2181, + "5621-util-format-float.ts": 2886, + "5622-events-once.mjs": 3520, + "5623-events-once.ts": 2509, + "5624-timers-promises-unawaited.ts": 2304, + "5625-promisify-exec.ts": 7463, + "5626-promisify-forms.ts": 2936, "5627-exec-callback.ts": 1900, + "5628-promisify-timers.ts": 3194, + "5629-promisify-stream.ts": 2517, + "5630-promisify-zlib.ts": 2503, "5631-promisify-crypto.ts": 2200, "5632-promisify-dns.ts": 1700, + "5633-zlib-typed-options.ts": 1800, "5644-net-socket-addresses.ts": 2220, "5645-net-socket-callbacks.ts": 2442, "5646-net-server-connections.ts": 2389, @@ -1844,7 +1984,87 @@ "5648-dns-lookup-forms.ts": 2992, "5649-dns-service-servers.ts": 2712, "5650-dns-lookup-errors-untyped.cjs": 2822, + "5651-tls-socket-protocol.ts": 2771, + "5652-http-client-trailer-overflow.ts": 3884, + "5653-net-socket-read-ahead.ts": 4524, + "5654-https-empty-servername.ts": 3451, + "5655-dgram-socket-options.ts": 3481, + "5656-dgram-option-states.ts": 3002, + "5657-net-reset-and-address.ts": 3415, + "5658-net-reset-dynamic.cjs": 2564, + "5659-net-blocklist-socketaddress.ts": 4443, + "5664-tls-client-identity.ts": 3727, + "5674-union-instanceof-array-arms.ts": 2159, + "5680-array-growth-after-copy.ts": 3791, "5681-array-pop-and-length.ts": 3754, + "5682-publish-wide-objects.ts": 3326, + "5683-date-local-fields-tz.ts": 3848, + "5700-cycle-release-retained.ts": 2156, + "5701-cycle-release-old-edges.ts": 2601, + "5702-publish-candidate-buffers.ts": 3469, + "5703-lifetime-region-debug.ts": 3115, + "5704-publish-refused-restores-counts.ts": 2979, + "5710-map-number-keys-hashed.ts": 3019, + "5711-map-number-write-after-read.ts": 2613, + "5712-map-number-overwrite-at-growth.ts": 2614, + "5714-cycle-large-candidate-buffers.ts": 3979, + "5715-json-parse-fitted-tables.ts": 3940, + "5717-set-prototype-of-primitives.ts": 3887, + "5719-int32-fields-dynamic-operators.ts": 3207, + "5720-intern-table-basics.ts": 3625, + "5721-intern-table-values.ts": 4014, + "5722-intern-table-workers.ts": 3210, + "5723-intern-table-shared-types.ts": 4601, + "5724-intern-table-array-keys.ts": 4072, + "5725-intern-table-callbacks.ts": 1996, + "5726-intern-table-key-limits.ts": 2644, + "5740-atomics-operation-semantics.ts": 4810, + "5741-atomics-contended-counters.ts": 2504, + "5742-atomics-lock-ordering.ts": 3000, + "5743-atomics-wait-notify.ts": 3638, + "5744-atomics-ring-buffer.ts": 3352, + "5745-atomics-semaphore-stress.ts": 3671, + "5746-shared-bulk-operations.ts": 2732, + "5747-fs-stat-throw-if-no-entry.ts": 2662, + "5748-fs-stat-throw-if-no-entry-js.cjs": 3085, + "5752-string-number-concat.ts": 3186, + "5760-crypto-hash-stream.ts": 3011, + "5761-crypto-hash-stream-states.ts": 3241, + "5762-crypto-hash-stream.cjs": 3286, + "5763-crypto-sha-blocks.ts": 3264, + "5764-class-structural-view-writes.ts": 4118, + "5764-crypto-hmac-edges.cjs": 2105, + "5765-class-structural-view-identity.ts": 3529, + "5766-class-structural-view-callbacks.ts": 3162, + "5767-class-opaque-accessor-view.ts": 2108, + "5768-crypto-hash-unknown.ts": 3470, + "5769-crypto-hash-unknown.cjs": 3490, + "5770-guarded-union-narrows.ts": 2881, + "5771-guarded-downcasts.ts": 3555, + "5772-guarded-catch-narrows.ts": 2601, + "5773-stale-narrowing-inherited.ts": 2486, + "5774-crypto-handle-long-callee.cjs": 1820, + "5776-power-of-two-remainder-integer-path.ts": 3263, + "5780-readline-history-options.ts": 2909, + "5781-readline-history-options.cjs": 3288, + "5782-readline-terminal-editing.ts": 2661, + "5783-readline-terminal-completion.cjs": 4948, + "5784-readline-terminal-promises.ts": 3042, + "5785-readline-terminal-keys.ts": 3376, + "5786-readline-terminal-history.cjs": 2745, + "5787-readline-cursor-helpers.cjs": 3241, + "5788-readline-promises-readline.ts": 3322, + "5789-readline-cursor-pos.cjs": 3206, + "5790-readline-keypress-events.ts": 3280, + "5791-dyn-constant-key-read-sites.ts": 2813, + "5791-readline-keypress-interfaces.cjs": 3397, + "5792-json-parse-repeated-layouts.ts": 3939, + "5792-readline-keypress-listeners.ts": 2151, + "5793-worker-message-channel.ts": 3274, + "5794-worker-message-port-close-order.ts": 3161, + "5795-worker-message-port-batches.ts": 1925, + "5796-worker-message-channel.cjs": 2678, + "5797-worker-message-port-transfer.ts": 3445, "5800-broadcast-channel.ts": 2899, "5801-broadcast-channel.cjs": 2727, "5802-broadcast-channel-workers.ts": 2809, @@ -1860,6 +2080,7 @@ "5812-dyn-box-async-fields.ts": 3522, "5813-mock-timers-module.ts": 3766, "5814-mock-timers-date.ts": 3760, + "5830-timers-promises-signal.ts": 3875, "5831-util-types-brands.ts": 3347, "5832-util-types-brands-dynamic.js": 3250, "5833-url-search-params-iterators.ts": 2678, @@ -1868,43 +2089,91 @@ "5836-url-search-params-tuple-pairs.ts": 2118, "5837-util-format-json-circular.ts": 3117, "5838-qs-stringify-bigint.ts": 2207, + "5839-timers-promises-scheduler.ts": 3195, + "5840-promise-with-resolvers-js.js": 2842, "5841-json-typed-dyn-members.ts": 5467, "5842-qs-parse-null-prototype.ts": 1774, "5843-qs-parse-null-prototype-js.mjs": 1559, "5844-date-unknown-values.ts": 3969, + "5845-groupby-null-prototype.ts": 2868, "5846-events-esm-default-max.ts": 2633, "5847-events-cjs-default-max.cjs": 2823, + "5849-class-view-observations.ts": 3073, + "5850-using-disposable-values.ts": 3391, + "5851-using-suppressed-error-values.ts": 2932, + "5852-using-disposable-values-js.js": 3132, + "5853-set-methods-map-arguments.ts": 3034, + "5854-array-from-async.ts": 4140, + "5855-array-from-async-js.js": 3324, + "5856-error-is-error.ts": 2937, "5857-regexp-unicode-sets.ts": 3350, "5858-disposable-stack.ts": 3050, "5859-disposable-stack-js.js": 3100, + "5860-url-ipv4-hosts.ts": 3101, "5870-worker-thread-names.ts": 3106, "5871-worker-untransferable.cjs": 2786, + "5872-readline-positional-create-interface.mjs": 3151, + "5873-readline-promises-namespace.cjs": 2023, "5874-worker-stdio-streams.ts": 2839, "5875-worker-stdout-pipe.cjs": 2740, "5879-worker-stdin.ts": 3158, + "5880-emitter-leak-warning-delivery.ts": 3418, "5881-inspect-weak-collections.ts": 2615, "5882-string-decoder-runtime-encoding.ts": 2541, "5883-os-set-priority.ts": 2380, "5884-inspect-layout-options.ts": 3108, + "5885-events-max-listener-helpers.ts": 2992, "5886-inspect-dates.ts": 2559, "5887-inspect-typed-arrays.ts": 3448, "5888-inspect-sorted.ts": 2998, + "5889-timers-scheduler-signal.ts": 2284, + "5890-while-string-length-counters.ts": 2516, + "5892-buffer-write-utf16le-runs.ts": 3107, "5900-float16-array.ts": 3000, "5901-float16-array-js.js": 2900, "5904-async-generator-completion.ts": 2950, "5905-array-from-async-generator.ts": 2750, "5906-array-from-async-generator-js.js": 2950, "5907-regex-groups-match-rows.ts": 3150, + "5908-iterator-helper-objects.ts": 6350, + "5909-iterator-helper-objects-js.js": 4850, "5910-set-prototype-of-primitive-object-kinds.ts": 2719, "5911-dyn-class-to-primitive.ts": 2850, "5912-dyn-class-conversion-methods.ts": 3251, "5913-prototype-type-error-messages.ts": 2484, "5914-dyn-in-class-prototype-members.ts": 3169, "5915-dyn-error-string-conversions.ts": 3200, + "5919-record-unions-shared-arms.ts": 3182, + "5920-promise-try-args.ts": 3028, + "5921-promise-try-args-js.js": 3179, "5922-matchall-groups-callbacks.ts": 3650, "5923-matchall-groups-js.js": 3970, + "5924-js-literal-identity.js": 2577, + "5925-class-view-identity-observers.ts": 2297, + "5926-class-view-switch-js.js": 2243, + "5927-switch-object-identity.ts": 2986, + "5928-groupby-iterables.ts": 3444, + "5929-groupby-js.js": 2997, + "5930-contract-int32-arrays.ts": 3715, + "5930-method-census-asserted-number-keys.ts": 3330, + "5930-record-union-pointers.ts": 3182, + "5931-asserted-reads-keep-number-parameters.ts": 2990, + "5931-record-union-unit-arms.ts": 2745, + "5932-generic-class-observed-methods.ts": 4272, + "5932-record-union-trees.ts": 2863, + "5933-class-fields-unknown-object.ts": 4189, + "5933-record-union-conversions.ts": 4140, + "5934-js-class-fields-property-order.js": 4613, + "5934-record-union-set-members.ts": 2265, + "5935-js-subclass-fields-and-element-calls.js": 2484, + "5936-checked-js-class-types.js": 3412, + "5937-override-narrowed-union-returns.ts": 3036, "5938-object-literal-computed-keys-keep-methods.ts": 3195, "5939-js-let-object-reassigned-wider.js": 1945, + "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, @@ -1917,23 +2186,62 @@ "5956-erased-generic-members.ts": 6730, "5958-asserted-union-receiver.ts": 2430, "5959-date-to-json.ts": 3330, + "5960-frame-records-own-references.ts": 3563, "5960-generic-mapped-types.ts": 2540, + "5961-frame-spread-updates.ts": 3260, + "5962-frame-tuples.ts": 3208, "5970-net-connect-host-name.ts": 2400, "5971-buffer-chunks-to-string.ts": 2700, "5972-net-set-encoding-listeners.ts": 2550, + "5973-buffer-concat-untyped.js": 1900, + "5973-large-closure-environment.ts": 4499, "5974-typeof-module-exports.cjs": 2000, "5975-zlib-class-values.ts": 2800, + "5976-builtin-class-aliases.ts": 3100, + "5977-builtin-class-aliases.cjs": 2950, + "5978-builtin-class-alias-modules/main.ts": 2750, + "5979-writable-instanceof-duplex.ts": 2700, "5990-worker-uncloneable.mjs": 3283, "5991-worker-async-dispose.mjs": 5699, "5992-worker-thread-name-lifetime.mjs": 2687, + "5993-array-length-loop-resizing.ts": 3187, + "5994-array-length-sparse-loops.ts": 3292, + "5995-array-dense-loop-boundaries.ts": 2596, + "5996-rejections-between-macrotasks.cjs": 3034, "600-closures-basic.ts": 2349, + "6000-json-stringify-class-instances.ts": 7567, + "6001-json-stringify-js-class-instances.js": 4668, "601-closures-loops.ts": 1788, "602-closures-identity-recursion.ts": 2372, "6020-class-extends-function-constructor.js": 4300, "6021-asserted-literal-union-arm.ts": 3400, "603-closures-rc-stress.ts": 1832, "604-closures-for-of.ts": 2639, + "6041-error-cause-redeclared.ts": 2988, + "6042-regex-match-object-destructuring.ts": 3393, + "6043-string-index-narrowed-union.ts": 2983, "605-closures-forward-capture-tdz.ts": 3277, + "6051-generic-class-factory-results.ts": 4363, + "6100-record-get-or-create.ts": 3771, + "6101-record-get-or-create-unchecked/main.ts": 2107, + "6103-typeof-builtin-bindings.cjs": 2202, + "6104-typeof-builtin-bindings.mjs": 2031, + "6105-cjs-top-level-this/main.js": 1371, + "6106-esm-top-level-this.mjs": 1915, + "6110-cjs-speculative-wrapper-fences.cjs": 1980, + "6140-cjs-require-tdz-throws/main.js": 2731, + "6141-cjs-require-tdz-checked/main.js": 2856, + "6142-cjs-require-published-early/main.js": 3400, + "6143-cjs-require-json/main.js": 3245, + "6144-cjs-require-builtin-nested/main.js": 2181, + "6145-cjs-require-cycle/main.js": 3507, + "6150-cjs-inline-require/main.js": 4200, + "6151-cjs-export-chains/main.js": 3000, + "6152-cjs-tsc-interop/main.js": 2600, + "6153-cjs-exports-object/main.js": 2400, + "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, "700-classes-basic.ts": 2530, "701-classes-composition.ts": 2357, "702-classes-this-capture.ts": 2140, @@ -2041,6 +2349,7 @@ "array-checked-callback-results.mjs": 3053, "array-checked-constructor.js": 3001, "array-concat-widening.ts": 2947, + "array-deque-unshift.ts": 3233, "array-element-compound-updates.ts": 3325, "array-fill-unit-values.ts": 2281, "array-from-checked.js": 3228, @@ -2049,6 +2358,7 @@ "array-numeric-own-keys.ts": 2770, "array-optional-number-write-index.ts": 2584, "array-primitive-ordering.ts": 3806, + "array-queue-head-room.ts": 2847, "array-read-helper-effects.ts": 3223, "array-refined-storage.ts": 3132, "array-sort-dense-effects.ts": 2813, @@ -2151,6 +2461,8 @@ "class-symbol-accessors.ts": 2458, "class-symbol-cache.mjs": 2883, "class-value-constructor-exceptions.ts": 2222, + "closed-virtual-dispatch-modules/main.ts": 2682, + "closed-virtual-dispatch.ts": 3944, "closure-nullable-union-return.ts": 2472, "closure-union-locals.ts": 2465, "collection-array-cycles.ts": 2552, @@ -2220,7 +2532,11 @@ "fresh-array-union-layout.js": 2480, "frozen-checked-dictionaries.mjs": 2629, "fs-native-realpath.ts": 2351, + "fs-null-byte-paths-js.cjs": 2762, + "fs-null-byte-paths.ts": 3411, "fs-read-encodings.ts": 2505, + "fs-read-utf8-edges.ts": 3246, + "fs-read-utf8-handle-forms.ts": 3163, "fs-remove-readonly.ts": 2354, "fs-write-string-bytes-union.ts": 2286, "function-apply-value.mjs": 2558, @@ -2247,6 +2563,7 @@ "generic-pattern-bindings.ts": 2698, "generic-record-index.ts": 2753, "generic-visitor-void.ts": 2405, + "global-accumulator-loops.ts": 3053, "global-error-subclasses.ts": 1773, "global-runtime-probes.js": 2288, "global-stored-probes.js": 2726, @@ -2288,6 +2605,8 @@ "js-array-iterators.js": 2559, "js-arrow-bind.cjs": 3343, "js-buffer-callback-brand.cjs": 2262, + "js-builder-string-method-names/main.ts": 4550, + "js-callable-builder.js": 4500, "js-callable-proxy.js": 3516, "js-chained-native-results.mjs": 2641, "js-checked-array-iterator.js": 2670, @@ -2299,6 +2618,7 @@ "js-class-added-parameters.js": 2472, "js-class-checked-overrides.js": 2571, "js-class-computed-fields.js": 1830, + "js-class-constructor-returns/main.ts": 4350, "js-class-enumeration.js": 3296, "js-class-field-accessor-override.js": 2567, "js-class-fluent-overrides.js": 2822, @@ -2322,6 +2642,7 @@ "js-dormant-array-write.js": 1888, "js-dormant-overrides.js": 2569, "js-expando-records.js": 2454, + "js-function-prototype-value.js": 3650, "js-implicit-call-results.js": 3064, "js-inferred-callback-exports/main.js": 3830, "js-inferred-class-array-return.js": 2570, @@ -2337,6 +2658,7 @@ "js-nested-native-descriptors.js": 2412, "js-normalize-dispatch.js": 2793, "js-nullish-inherited-fields.mjs": 2710, + "js-number-parse-dynamic.js": 2700, "js-object-receiver-storage.js": 2613, "js-object-returning-constructors.mjs": 2186, "js-object-shared-arrays.js": 2465, @@ -2345,6 +2667,7 @@ "js-optional-class-fields.js": 2266, "js-optional-member-call.js": 1883, "js-primitive-union-addition.js": 2256, + "js-process-destructure.mjs": 4750, "js-promise-all-values.js": 3079, "js-promise-catch-handler.js": 1749, "js-promise-finally-order.js": 1913, @@ -2352,6 +2675,7 @@ "js-prototype-host-boundary.js": 2224, "js-reflect-receivers.js": 3119, "js-regex-record-captures.js": 2010, + "js-reshaped-object-literal.js": 5700, "js-scalar-iterables.js": 2477, "js-shared-method-dispatch.js": 2033, "js-specialized-field-method.js": 2335, @@ -2365,6 +2689,9 @@ "json-runtime-options.mjs": 1997, "json-stringify-value.js": 2563, "json-typed-parse-direct.ts": 2355, + "json-typed-parse-index-signatures.ts": 3686, + "json-typed-parse-optional-members.ts": 3044, + "json-typed-parse-union-members.ts": 3090, "literal-switch-dispatch.ts": 2500, "live-reference-shared-converters.ts": 2552, "llvm-read-receiver-lifetime.ts": 2343, @@ -2386,6 +2713,7 @@ "loop-reference-lifetimes.ts": 2542, "map-checked-boundary/main.ts": 2629, "map-checked-seed.js": 3013, + "map-get-set-fusion.ts": 3284, "map-number-keys-dense.ts": 2562, "map-read-lifetimes-exits.ts": 2227, "map-read-lifetimes.ts": 2725, @@ -2577,6 +2905,8 @@ "promise-resolve-void.ts": 2341, "prototype-method-overrides.mjs": 4870, "prototype-named-data-records.ts": 2513, + "rc-last-use-moves.ts": 2854, + "rc-private-array-writes.ts": 3076, "record-field-presence.ts": 3025, "record-key-order.ts": 2807, "record-optional-json-presence.ts": 2623, @@ -2592,6 +2922,8 @@ "regex-checked-casts.ts": 2350, "regex-checked-storage.js": 2669, "regex-exec-long-subject.ts": 4533, + "regex-exec-result-arrays.ts": 3204, + "regex-fs-text-surfaces.ts": 3720, "regex-input-lifetimes.ts": 2535, "regex-native-matcher.ts": 3683, "regex-optional-captures.ts": 2606, @@ -2616,12 +2948,20 @@ "set-iterator-seeds.ts": 2896, "set-optional-seeds.ts": 2121, "small-collection-transitions.ts": 2645, + "speculative-integer-globals.ts": 2544, + "speculative-integer-loops.ts": 2308, + "speculative-integer-reads.ts": 3059, "stack-depth-async-bodies.ts": 1910, "stack-overflow-async.ts": 2447, "stack-overflow-caught.ts": 2424, "stack-overflow-finally.ts": 2613, "stack-overflow-shapes.ts": 2534, "stack-overflow-uncaught.ts": 2317, + "stackless-async-concurrency.ts": 3322, + "stackless-async-context.ts": 1853, + "stackless-async-exceptions.ts": 3251, + "stackless-async-ordering.ts": 3551, + "stackless-async-unhandled.ts": 1779, "static-field-updates/main.ts": 2050, "static-method-receivers.ts": 2243, "stdio-values.mjs": 2578, @@ -2629,10 +2969,15 @@ "stdio-write-overrides.mjs": 2495, "stored-native-builtins.js": 2831, "stored-object-helpers.js": 2747, + "stream-dynamic-entry-kinds.cjs": 2778, + "stream-dynamic-entry-kinds.ts": 2624, + "stream-encoded-from-entries.cjs": 2924, + "stream-encoded-from-entries.ts": 3274, "string-char-code-integer-loops.ts": 2495, "string-code-points.ts": 1970, "string-concat-number-parts.ts": 2044, "string-construction-order.ts": 2502, + "string-field-append.ts": 2926, "string-lifetimes-boundaries.ts": 2481, "string-lifetimes-evaluation.ts": 2486, "string-lifetimes-iteration.ts": 2043, @@ -2659,6 +3004,7 @@ "text-codec-var.ts": 2182, "text-encode-into-boundaries.ts": 1971, "timers-clear-many.ts": 2704, + "truthy-object-logical.ts": 5950, "tuple-and-string-search.ts": 2663, "tuple-array-union.ts": 2706, "tuple-callback-visitation.ts": 3464, @@ -4053,6 +4399,8 @@ "4451-int32-field-storage.ts": 3668, "4452-int32-field-dynamic-writes.ts": 4531, "4453-int32-closed-locals.ts": 2786, + "4454-int32-field-reflection-reads.ts": 3494, + "4455-live-view-reads-without-stores.ts": 4871, "4471-unassigned-field-reads.ts": 3896, "4476-string-ascii-header-fact.ts": 3522, "4477-buffer-write-ascii-runs.ts": 3321, @@ -4221,6 +4569,8 @@ "5020-lifetime-region-large.ts": 4812, "5021-lifetime-region-opaque-holders.ts": 3957, "5022-lifetime-region-workers.ts": 38841, + "5023-lifetime-region-await.ts": 5986, + "5024-lifetime-region-await-forms.ts": 5123, "503-array-functions.ts": 2982, "504-array-rc-stress.ts": 3289, "5040-promise-all-race-turns.ts": 4439, @@ -4402,6 +4752,16 @@ "5248-event-target-subclass-warnings.ts": 4513, "5249-event-target-subclass-unconstructed.ts": 2679, "525-map-rc-stress.ts": 2764, + "5250-cipher-aes-gcm.ts": 8740, + "5251-cipher-aes-cbc.ts": 42449, + "5252-cipher-aes-ctr.ts": 26060, + "5253-cipher-chacha20-poly1305.ts": 8385, + "5254-cipher-errors.js": 28463, + "5255-cipher-encodings.cjs": 25164, + "5256-cipher-deprecation.cjs": 45696, + "5257-cipher-vectors.ts": 7138, + "5258-cipher-handles.js": 24870, + "5259-cipher-chunked.ts": 7052, "526-set-basics.ts": 2480, "5260-record-unknown-identity.ts": 3505, "5261-optional-field-identity.ts": 3914, @@ -4441,6 +4801,14 @@ "5319-map-iteration-values.ts": 4062, "532-record-arrays-rc-stress.ts": 2708, "5320-map-iterate-across-compaction.ts": 2736, + "5321-integer-closed-arithmetic-loops.ts": 3710, + "5321-json-parse-cycles.ts": 3383, + "5322-integer-variable-step-loops.ts": 3384, + "5322-json-stringify-parsed-trees.ts": 3838, + "5323-dyn-numeric-index-reads.ts": 3192, + "5323-int32-nonnegative-slot-facts.ts": 3774, + "5324-int32-initializer-locals.ts": 2663, + "5324-structured-clone-documents.ts": 4264, "5325-record-boundary-returns.ts": 3046, "5326-record-boundary-params.ts": 2989, "5327-record-boundary-identity.ts": 3529, @@ -4449,14 +4817,36 @@ "533-array-element-cycles.ts": 3118, "5330-record-boundary-fields.ts": 3048, "5331-record-boundary-methods.ts": 2560, + "5332-int32-narrowed-slot-facts.ts": 3588, + "5333-integer-guarded-while-counters.ts": 3896, "5333-record-boundary-nullable-argument.ts": 3100, "5334-fetch-mentions-without-calls.ts": 4032, + "5335-stream-operators-values.ts": 6378, "5335-top-level-await-pending-exit-code.ts": 2383, + "5336-stream-operators-promise-timing.ts": 4282, "5336-top-level-await-pending-exit-listener.ts": 2814, "5337-dc-subscriber-throw-next-tick.cjs": 3273, + "5337-stream-operators-concurrency.ts": 8858, "5338-js-tdz-function-element.js": 3733, + "5338-stream-operators-checks.cjs": 5284, "5339-array-buffer-constructor.js": 2884, + "5339-stream-for-await-hops.ts": 5704, "534-record-width-subtyping.ts": 3530, + "5340-readable-from-options.ts": 4031, + "5341-compound-any-operand.ts": 3905, + "5341-generator-callback-params.ts": 4564, + "5341-integer-recursion-boundaries.ts": 4375, + "5341-readable-from-dynamic.cjs": 5807, + "5342-compound-any-operand.cjs": 3381, + "5342-generator-scalar-channels.ts": 3638, + "5342-integer-recursion-stack-and-mutual.ts": 3513, + "5342-stream-compose.ts": 7562, + "5343-any-flow-compound.ts": 2631, + "5343-integer-recursion-array-index.ts": 4214, + "5343-iterator-result-records.ts": 2610, + "5343-stream-compose-edges.ts": 10326, + "5344-json-parse-undeclared-keys.ts": 5823, + "5344-stream-pipeline-timing.ts": 4092, "5345-object-values-for-of-unknown.ts": 4013, "5346-object-values-length-dynamic.ts": 3930, "5347-object-values-loop-control.ts": 3598, @@ -4465,6 +4855,12 @@ "535-object-statics.ts": 2862, "5350-object-values-wide-record.ts": 3272, "5351-object-values-methods-index.ts": 3637, + "5352-final-field-loop-reads.ts": 4118, + "5353-captured-const-values.ts": 3887, + "5354-captured-const-tdz.ts": 3465, + "5355-captured-function-declarations.ts": 4030, + "5356-captured-tdz-ordering.ts": 4031, + "5357-init-only-module-bindings.ts": 3717, "536-map-seed-array.ts": 3237, "5360-array-sort-comparator-calls.ts": 4265, "5361-array-sort-inconsistent-comparators.ts": 4181, @@ -4473,6 +4869,16 @@ "5364-array-sort-union-and-boolean-elements.ts": 14300, "5365-array-sort-dynamic-values.js": 2919, "537-map-iter-drains.ts": 3050, + "5370-zlib-stream-roundtrip.ts": 4546, + "5371-zlib-stream-chunking.cjs": 6071, + "5372-zlib-stream-errors.cjs": 6496, + "5373-zlib-stream-options.cjs": 6767, + "5374-zlib-stream-flush-params.ts": 5113, + "5375-zlib-stream-promises.ts": 6129, + "5376-zlib-constants.cjs": 3393, + "5377-zlib-stream-backpressure.cjs": 4992, + "5378-zlib-sync-options.cjs": 4120, + "5379-zlib-callback-options.ts": 4524, "538-map-set-for-of.ts": 3029, "5390-interface-loop-element-lifetimes.ts": 3566, "5391-interface-dispatch-hierarchies.ts": 3731, @@ -4494,7 +4900,18 @@ "5411-inspect-class-hierarchies.ts": 2423, "5412-inspect-dynamic-collections.ts": 3042, "5413-inspect-class-values.cjs": 3217, + "5415-zlib-dynamic-inputs.cjs": 4054, "542-union-element-arrays.ts": 2812, + "5420-readable-from-generators.ts": 5843, + "5421-readable-from-generators.cjs": 3752, + "5422-stream-async-push-wakeup.ts": 3679, + "5423-stream-end-and-eos-timing.ts": 4453, + "5424-stream-write-buffer-encoding.ts": 3204, + "5425-pipeline-generator-stage.ts": 3700, + "5426-pipeline-generator-errors.ts": 4597, + "5427-pipeline-generator-backpressure.ts": 4175, + "5428-pipeline-generator-stages.cjs": 9702, + "5429-pipeline-generator-buffers.ts": 6406, "5430-dyn-number-string-boxes.ts": 3930, "5435-crypto-hash-algorithms.ts": 3451, "5436-crypto-hash-names.js": 3185, @@ -4525,30 +4942,99 @@ "5462-locale-compare-probe-sweep.ts": 4164, "5463-locale-compare-combining-letters.ts": 3755, "5464-locale-compare-fractions.ts": 3312, + "5465-dyn-index-read-arrays.ts": 3798, + "5466-dyn-index-read-special.ts": 4384, "5470-strip-vt-osc.cjs": 2708, "5471-strip-vt-csi.cjs": 3038, "5472-strip-vt-random.cjs": 2850, "5473-strip-vt-unterminated.cjs": 4462, "5474-strip-vt-surrogates.ts": 3554, + "5510-assert-error-properties.js": 5349, + "5511-assert-error-class.ts": 4329, + "5512-assert-error-options.js": 3469, + "5513-assert-deep-equal-loose.js": 4138, + "5514-assert-deep-equal-typed.ts": 3792, + "5515-assert-deep-strict-typed-messages.ts": 3601, + "5516-assert-deep-equal-edges.js": 4686, + "5517-assert-partial-deep-equal.js": 2953, + "5518-assert-partial-deep-typed.ts": 4685, + "5519-assert-partial-deep-typed-edges.ts": 4346, + "5520-assert-record-literal-failures.ts": 4088, + "5520-dyn-property-read-receivers.ts": 4057, + "5521-assert-empty-message.js": 3734, + "5521-dyn-property-read-keys.ts": 3703, + "5522-assert-throws-failure-properties.ts": 4256, + "5523-assert-class-options.js": 4903, + "5524-assert-class-skip-prototype.ts": 4411, + "5530-buffer-pool.ts": 4413, + "5531-buffer-pool-first-slab.ts": 5557, + "5532-buffer-pool-first-slab-untyped.mjs": 220640, "5540-fs-callback-forms.cjs": 3079, "5541-fs-stats-fields.cjs": 4039, "5542-fs-promises-parity.cjs": 3312, "5543-fs-promises-typed.ts": 2944, "5544-fs-opendir.cjs": 3622, "5545-fs-options-encoding.cjs": 3497, + "5546-fs-read-stream.cjs": 5295, + "5547-fs-write-stream.cjs": 6075, "5548-fs-callback-arguments.cjs": 3905, "5549-fs-callback-option-checks.cjs": 3471, "5550-locale-compare-ascii-words.ts": 2824, "5551-array-search-references.ts": 3964, "5552-string-appends.ts": 3653, "5553-string-append-char-code.ts": 3292, + "5554-fs-streams-typed.ts": 4873, + "5555-fs-callback-turns.cjs": 3708, + "5555-http-server-close-keep-alive.ts": 15618, + "5556-http-client-agent-connection.ts": 4177, + "5557-http-outgoing-header-values.ts": 4083, + "5558-http-incoming-duplicate-headers.ts": 2888, + "5559-http-expect-header.ts": 14863, + "5560-http-response-framing.ts": 11391, + "5561-http-max-header-size.ts": 4316, + "5562-https-verify-errors.ts": 4777, + "5563-http-keep-alive-timeout.ts": 17783, + "5564-http-request-signal.ts": 10735, "5565-fs-dirent-untyped.mjs": 2898, "5566-fs-dirent-dyn-boundaries/main.mjs": 3333, + "5567-fs-cpsync.ts": 4945, + "5568-fs-cp-errors.js": 4099, + "5569-fs-cp-symlinks.ts": 3686, + "5570-fs-cp-async.ts": 4176, + "5571-fs-cp-deep.ts": 5306, "5585-record-results-inlined.ts": 3681, "5595-net-ref-unref.ts": 3036, + "5600-dyn-cycle-self.ts": 3365, + "5601-dyn-cycle-mutual.ts": 4239, + "5602-dyn-cycle-arrays.ts": 3453, + "5603-dyn-cycle-identity.ts": 3497, + "5604-dyn-cycle-checks.ts": 3761, + "5605-dyn-cycle-containers.ts": 4084, + "5606-dyn-cycle-capsules.ts": 3095, + "5610-readable-from-sets-and-views.ts": 5921, + "5611-readable-from-class-iterables.ts": 3857, + "5612-readable-from-iterables.cjs": 5191, + "5613-readable-from-iterable-timing.cjs": 5131, + "5614-readable-from-iterable-errors.cjs": 4553, + "5615-next-tick-callback-phases.ts": 4347, + "5616-next-tick-callback-phases.cjs": 3259, + "5617-pipeline-function-ends.ts": 60581, + "5618-pipeline-function-ends-errors.ts": 13092, + "5619-pipeline-function-ends.cjs": 14768, + "5620-timers-promises-values.ts": 3726, + "5621-util-format-float.ts": 3395, + "5622-events-once.mjs": 3355, + "5623-events-once.ts": 4676, + "5624-timers-promises-unawaited.ts": 4420, + "5625-promisify-exec.ts": 8488, + "5626-promisify-forms.ts": 3100, "5627-exec-callback.ts": 2500, + "5628-promisify-timers.ts": 3913, + "5629-promisify-stream.ts": 3357, + "5630-promisify-zlib.ts": 3392, "5631-promisify-crypto.ts": 2600, "5632-promisify-dns.ts": 2000, + "5633-zlib-typed-options.ts": 2200, "5644-net-socket-addresses.ts": 2393, "5645-net-socket-callbacks.ts": 2060, "5646-net-server-connections.ts": 2755, @@ -4556,7 +5042,87 @@ "5648-dns-lookup-forms.ts": 3416, "5649-dns-service-servers.ts": 4916, "5650-dns-lookup-errors-untyped.cjs": 3889, + "5651-tls-socket-protocol.ts": 30873, + "5652-http-client-trailer-overflow.ts": 3512, + "5653-net-socket-read-ahead.ts": 6212, + "5654-https-empty-servername.ts": 6322, + "5655-dgram-socket-options.ts": 5089, + "5656-dgram-option-states.ts": 4267, + "5657-net-reset-and-address.ts": 3588, + "5658-net-reset-dynamic.cjs": 4002, + "5659-net-blocklist-socketaddress.ts": 5090, + "5664-tls-client-identity.ts": 6340, + "5674-union-instanceof-array-arms.ts": 3943, + "5680-array-growth-after-copy.ts": 4581, "5681-array-pop-and-length.ts": 3216, + "5682-publish-wide-objects.ts": 3376, + "5683-date-local-fields-tz.ts": 3911, + "5700-cycle-release-retained.ts": 3414, + "5701-cycle-release-old-edges.ts": 4210, + "5702-publish-candidate-buffers.ts": 3848, + "5703-lifetime-region-debug.ts": 2875, + "5704-publish-refused-restores-counts.ts": 3759, + "5710-map-number-keys-hashed.ts": 3686, + "5711-map-number-write-after-read.ts": 3642, + "5712-map-number-overwrite-at-growth.ts": 4057, + "5714-cycle-large-candidate-buffers.ts": 4303, + "5715-json-parse-fitted-tables.ts": 4861, + "5717-set-prototype-of-primitives.ts": 3977, + "5719-int32-fields-dynamic-operators.ts": 4013, + "5720-intern-table-basics.ts": 5183, + "5721-intern-table-values.ts": 4244, + "5722-intern-table-workers.ts": 37693, + "5723-intern-table-shared-types.ts": 25115, + "5724-intern-table-array-keys.ts": 43939, + "5725-intern-table-callbacks.ts": 3924, + "5726-intern-table-key-limits.ts": 3414, + "5740-atomics-operation-semantics.ts": 4541, + "5741-atomics-contended-counters.ts": 5176, + "5742-atomics-lock-ordering.ts": 44911, + "5743-atomics-wait-notify.ts": 25944, + "5744-atomics-ring-buffer.ts": 3295, + "5745-atomics-semaphore-stress.ts": 39641, + "5746-shared-bulk-operations.ts": 3506, + "5747-fs-stat-throw-if-no-entry.ts": 3747, + "5748-fs-stat-throw-if-no-entry-js.cjs": 4239, + "5752-string-number-concat.ts": 2903, + "5760-crypto-hash-stream.ts": 3720, + "5761-crypto-hash-stream-states.ts": 3947, + "5762-crypto-hash-stream.cjs": 3953, + "5763-crypto-sha-blocks.ts": 4280, + "5764-class-structural-view-writes.ts": 4359, + "5764-crypto-hmac-edges.cjs": 3178, + "5765-class-structural-view-identity.ts": 4251, + "5766-class-structural-view-callbacks.ts": 3950, + "5767-class-opaque-accessor-view.ts": 3507, + "5768-crypto-hash-unknown.ts": 3960, + "5769-crypto-hash-unknown.cjs": 4090, + "5770-guarded-union-narrows.ts": 3945, + "5771-guarded-downcasts.ts": 3443, + "5772-guarded-catch-narrows.ts": 3695, + "5773-stale-narrowing-inherited.ts": 3758, + "5774-crypto-handle-long-callee.cjs": 2420, + "5776-power-of-two-remainder-integer-path.ts": 3584, + "5780-readline-history-options.ts": 3008, + "5781-readline-history-options.cjs": 5419, + "5782-readline-terminal-editing.ts": 5323, + "5783-readline-terminal-completion.cjs": 5914, + "5784-readline-terminal-promises.ts": 8841, + "5785-readline-terminal-keys.ts": 7210, + "5786-readline-terminal-history.cjs": 5018, + "5787-readline-cursor-helpers.cjs": 4120, + "5788-readline-promises-readline.ts": 3931, + "5789-readline-cursor-pos.cjs": 10833, + "5790-readline-keypress-events.ts": 5781, + "5791-dyn-constant-key-read-sites.ts": 3910, + "5791-readline-keypress-interfaces.cjs": 10593, + "5792-json-parse-repeated-layouts.ts": 4520, + "5792-readline-keypress-listeners.ts": 3779, + "5793-worker-message-channel.ts": 6071, + "5794-worker-message-port-close-order.ts": 4996, + "5795-worker-message-port-batches.ts": 2995, + "5796-worker-message-channel.cjs": 3812, + "5797-worker-message-port-transfer.ts": 3633, "5800-broadcast-channel.ts": 28374, "5801-broadcast-channel.cjs": 27251, "5802-broadcast-channel-workers.ts": 27251, @@ -4572,6 +5138,7 @@ "5812-dyn-box-async-fields.ts": 5278, "5813-mock-timers-module.ts": 4227, "5814-mock-timers-date.ts": 17708, + "5830-timers-promises-signal.ts": 4170, "5831-util-types-brands.ts": 4380, "5832-util-types-brands-dynamic.js": 3658, "5833-url-search-params-iterators.ts": 5339, @@ -4580,43 +5147,91 @@ "5836-url-search-params-tuple-pairs.ts": 3193, "5837-util-format-json-circular.ts": 3408, "5838-qs-stringify-bigint.ts": 3519, + "5839-timers-promises-scheduler.ts": 3897, + "5840-promise-with-resolvers-js.js": 3596, "5841-json-typed-dyn-members.ts": 3238, "5842-qs-parse-null-prototype.ts": 7077, "5843-qs-parse-null-prototype-js.mjs": 5395, "5844-date-unknown-values.ts": 4543, + "5845-groupby-null-prototype.ts": 4290, "5846-events-esm-default-max.ts": 3102, "5847-events-cjs-default-max.cjs": 3039, + "5849-class-view-observations.ts": 4659, + "5850-using-disposable-values.ts": 3072, + "5851-using-suppressed-error-values.ts": 3950, + "5852-using-disposable-values-js.js": 3905, + "5853-set-methods-map-arguments.ts": 3831, + "5854-array-from-async.ts": 5635, + "5855-array-from-async-js.js": 3433, + "5856-error-is-error.ts": 3928, "5857-regexp-unicode-sets.ts": 3900, "5858-disposable-stack.ts": 3750, "5859-disposable-stack-js.js": 3600, + "5860-url-ipv4-hosts.ts": 4018, "5870-worker-thread-names.ts": 31697, "5871-worker-untransferable.cjs": 27481, + "5872-readline-positional-create-interface.mjs": 6166, + "5873-readline-promises-namespace.cjs": 3811, "5874-worker-stdio-streams.ts": 27801, "5875-worker-stdout-pipe.cjs": 31391, "5879-worker-stdin.ts": 33232, + "5880-emitter-leak-warning-delivery.ts": 4319, "5881-inspect-weak-collections.ts": 2915, "5882-string-decoder-runtime-encoding.ts": 3854, "5883-os-set-priority.ts": 3417, "5884-inspect-layout-options.ts": 3596, + "5885-events-max-listener-helpers.ts": 3588, "5886-inspect-dates.ts": 2736, "5887-inspect-typed-arrays.ts": 2997, "5888-inspect-sorted.ts": 2861, + "5889-timers-scheduler-signal.ts": 8928, + "5890-while-string-length-counters.ts": 3822, + "5892-buffer-write-utf16le-runs.ts": 3465, "5900-float16-array.ts": 3750, "5901-float16-array-js.js": 3700, "5904-async-generator-completion.ts": 3800, "5905-array-from-async-generator.ts": 3500, "5906-array-from-async-generator-js.js": 3750, "5907-regex-groups-match-rows.ts": 3750, + "5908-iterator-helper-objects.ts": 5950, + "5909-iterator-helper-objects-js.js": 3750, "5910-set-prototype-of-primitive-object-kinds.ts": 5033, "5911-dyn-class-to-primitive.ts": 4732, "5912-dyn-class-conversion-methods.ts": 4424, "5913-prototype-type-error-messages.ts": 4068, "5914-dyn-in-class-prototype-members.ts": 4327, "5915-dyn-error-string-conversions.ts": 4247, + "5919-record-unions-shared-arms.ts": 4941, + "5920-promise-try-args.ts": 3195, + "5921-promise-try-args-js.js": 3082, "5922-matchall-groups-callbacks.ts": 5960, "5923-matchall-groups-js.js": 6650, + "5924-js-literal-identity.js": 3065, + "5925-class-view-identity-observers.ts": 2685, + "5926-class-view-switch-js.js": 3057, + "5927-switch-object-identity.ts": 2661, + "5928-groupby-iterables.ts": 4500, + "5929-groupby-js.js": 3482, + "5930-contract-int32-arrays.ts": 4745, + "5930-method-census-asserted-number-keys.ts": 4815, + "5930-record-union-pointers.ts": 4941, + "5931-asserted-reads-keep-number-parameters.ts": 3015, + "5931-record-union-unit-arms.ts": 4558, + "5932-generic-class-observed-methods.ts": 5678, + "5932-record-union-trees.ts": 4329, + "5933-class-fields-unknown-object.ts": 4834, + "5933-record-union-conversions.ts": 4357, + "5934-js-class-fields-property-order.js": 4883, + "5934-record-union-set-members.ts": 4003, + "5935-js-subclass-fields-and-element-calls.js": 4470, + "5936-checked-js-class-types.js": 4183, + "5937-override-narrowed-union-returns.ts": 3827, "5938-object-literal-computed-keys-keep-methods.ts": 7194, "5939-js-let-object-reassigned-wider.js": 4436, + "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, @@ -4629,23 +5244,62 @@ "5956-erased-generic-members.ts": 8750, "5958-asserted-union-receiver.ts": 3160, "5959-date-to-json.ts": 4330, + "5960-frame-records-own-references.ts": 3535, "5960-generic-mapped-types.ts": 3300, + "5961-frame-spread-updates.ts": 3732, + "5962-frame-tuples.ts": 3372, "5970-net-connect-host-name.ts": 2800, "5971-buffer-chunks-to-string.ts": 3100, "5972-net-set-encoding-listeners.ts": 3000, + "5973-buffer-concat-untyped.js": 2900, + "5973-large-closure-environment.ts": 5145, "5974-typeof-module-exports.cjs": 2500, "5975-zlib-class-values.ts": 4200, + "5976-builtin-class-aliases.ts": 5450, + "5977-builtin-class-aliases.cjs": 4300, + "5978-builtin-class-alias-modules/main.ts": 4450, + "5979-writable-instanceof-duplex.ts": 6000, "5990-worker-uncloneable.mjs": 22432, "5991-worker-async-dispose.mjs": 25768, "5992-worker-thread-name-lifetime.mjs": 28932, + "5993-array-length-loop-resizing.ts": 4127, + "5994-array-length-sparse-loops.ts": 2815, + "5995-array-dense-loop-boundaries.ts": 4171, + "5996-rejections-between-macrotasks.cjs": 22172, "600-closures-basic.ts": 2534, + "6000-json-stringify-class-instances.ts": 12107, + "6001-json-stringify-js-class-instances.js": 7469, "601-closures-loops.ts": 2297, "602-closures-identity-recursion.ts": 2427, "6020-class-extends-function-constructor.js": 7500, "6021-asserted-literal-union-arm.ts": 7200, "603-closures-rc-stress.ts": 2589, "604-closures-for-of.ts": 3514, + "6041-error-cause-redeclared.ts": 4631, + "6042-regex-match-object-destructuring.ts": 3091, + "6043-string-index-narrowed-union.ts": 3627, "605-closures-forward-capture-tdz.ts": 4638, + "6051-generic-class-factory-results.ts": 46581, + "6100-record-get-or-create.ts": 3173, + "6101-record-get-or-create-unchecked/main.ts": 3228, + "6103-typeof-builtin-bindings.cjs": 1796, + "6104-typeof-builtin-bindings.mjs": 1715, + "6105-cjs-top-level-this/main.js": 3213, + "6106-esm-top-level-this.mjs": 1672, + "6110-cjs-speculative-wrapper-fences.cjs": 2500, + "6140-cjs-require-tdz-throws/main.js": 2989, + "6141-cjs-require-tdz-checked/main.js": 4062, + "6142-cjs-require-published-early/main.js": 5156, + "6143-cjs-require-json/main.js": 3050, + "6144-cjs-require-builtin-nested/main.js": 3329, + "6145-cjs-require-cycle/main.js": 4761, + "6150-cjs-inline-require/main.js": 9600, + "6151-cjs-export-chains/main.js": 7000, + "6152-cjs-tsc-interop/main.js": 6000, + "6153-cjs-exports-object/main.js": 5600, + "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, "700-classes-basic.ts": 2674, "701-classes-composition.ts": 2606, "702-classes-this-capture.ts": 2403, @@ -4753,6 +5407,7 @@ "array-checked-callback-results.mjs": 3402, "array-checked-constructor.js": 3477, "array-concat-widening.ts": 3858, + "array-deque-unshift.ts": 4273, "array-element-compound-updates.ts": 3591, "array-fill-unit-values.ts": 2572, "array-from-checked.js": 3097, @@ -4761,6 +5416,7 @@ "array-numeric-own-keys.ts": 3632, "array-optional-number-write-index.ts": 3380, "array-primitive-ordering.ts": 3925, + "array-queue-head-room.ts": 5069, "array-read-helper-effects.ts": 3425, "array-refined-storage.ts": 3909, "array-sort-dense-effects.ts": 2917, @@ -4863,6 +5519,8 @@ "class-symbol-accessors.ts": 2479, "class-symbol-cache.mjs": 3721, "class-value-constructor-exceptions.ts": 3210, + "closed-virtual-dispatch-modules/main.ts": 3375, + "closed-virtual-dispatch.ts": 4025, "closure-nullable-union-return.ts": 2727, "closure-union-locals.ts": 2878, "collection-array-cycles.ts": 3240, @@ -4932,7 +5590,11 @@ "fresh-array-union-layout.js": 2679, "frozen-checked-dictionaries.mjs": 3220, "fs-native-realpath.ts": 2477, + "fs-null-byte-paths-js.cjs": 3914, + "fs-null-byte-paths.ts": 5084, "fs-read-encodings.ts": 3111, + "fs-read-utf8-edges.ts": 4161, + "fs-read-utf8-handle-forms.ts": 3562, "fs-remove-readonly.ts": 2708, "fs-write-string-bytes-union.ts": 2772, "function-apply-value.mjs": 2976, @@ -4959,6 +5621,7 @@ "generic-pattern-bindings.ts": 3518, "generic-record-index.ts": 3239, "generic-visitor-void.ts": 3402, + "global-accumulator-loops.ts": 3531, "global-error-subclasses.ts": 2754, "global-runtime-probes.js": 2804, "global-stored-probes.js": 3113, @@ -5000,6 +5663,8 @@ "js-array-iterators.js": 3215, "js-arrow-bind.cjs": 3596, "js-buffer-callback-brand.cjs": 3526, + "js-builder-string-method-names/main.ts": 5400, + "js-callable-builder.js": 10100, "js-callable-proxy.js": 3865, "js-chained-native-results.mjs": 3076, "js-checked-array-iterator.js": 2790, @@ -5011,6 +5676,7 @@ "js-class-added-parameters.js": 2522, "js-class-checked-overrides.js": 2542, "js-class-computed-fields.js": 2281, + "js-class-constructor-returns/main.ts": 5400, "js-class-enumeration.js": 3084, "js-class-field-accessor-override.js": 3865, "js-class-fluent-overrides.js": 3368, @@ -5034,6 +5700,7 @@ "js-dormant-array-write.js": 2014, "js-dormant-overrides.js": 3783, "js-expando-records.js": 2526, + "js-function-prototype-value.js": 5100, "js-implicit-call-results.js": 2892, "js-inferred-callback-exports/main.js": 3776, "js-inferred-class-array-return.js": 3477, @@ -5049,6 +5716,7 @@ "js-nested-native-descriptors.js": 2725, "js-normalize-dispatch.js": 3264, "js-nullish-inherited-fields.mjs": 3080, + "js-number-parse-dynamic.js": 4350, "js-object-receiver-storage.js": 3515, "js-object-returning-constructors.mjs": 3015, "js-object-shared-arrays.js": 2539, @@ -5057,6 +5725,7 @@ "js-optional-class-fields.js": 3252, "js-optional-member-call.js": 3095, "js-primitive-union-addition.js": 2779, + "js-process-destructure.mjs": 4600, "js-promise-all-values.js": 3443, "js-promise-catch-handler.js": 2034, "js-promise-finally-order.js": 2084, @@ -5064,6 +5733,7 @@ "js-prototype-host-boundary.js": 4779, "js-reflect-receivers.js": 3486, "js-regex-record-captures.js": 2214, + "js-reshaped-object-literal.js": 7750, "js-scalar-iterables.js": 3719, "js-shared-method-dispatch.js": 2114, "js-specialized-field-method.js": 3011, @@ -5077,6 +5747,9 @@ "json-runtime-options.mjs": 2794, "json-stringify-value.js": 2833, "json-typed-parse-direct.ts": 3580, + "json-typed-parse-index-signatures.ts": 3707, + "json-typed-parse-optional-members.ts": 4645, + "json-typed-parse-union-members.ts": 2884, "literal-switch-dispatch.ts": 2951, "live-reference-shared-converters.ts": 19276, "llvm-read-receiver-lifetime.ts": 3803, @@ -5098,6 +5771,7 @@ "loop-reference-lifetimes.ts": 3339, "map-checked-boundary/main.ts": 2843, "map-checked-seed.js": 2966, + "map-get-set-fusion.ts": 3638, "map-number-keys-dense.ts": 2773, "map-read-lifetimes-exits.ts": 2761, "map-read-lifetimes.ts": 3024, @@ -5289,6 +5963,8 @@ "promise-resolve-void.ts": 2846, "prototype-method-overrides.mjs": 4119, "prototype-named-data-records.ts": 3179, + "rc-last-use-moves.ts": 4127, + "rc-private-array-writes.ts": 4075, "record-field-presence.ts": 3698, "record-key-order.ts": 2706, "record-optional-json-presence.ts": 3216, @@ -5304,6 +5980,8 @@ "regex-checked-casts.ts": 2405, "regex-checked-storage.js": 3560, "regex-exec-long-subject.ts": 5584, + "regex-exec-result-arrays.ts": 4036, + "regex-fs-text-surfaces.ts": 4013, "regex-input-lifetimes.ts": 3241, "regex-native-matcher.ts": 3652, "regex-optional-captures.ts": 2740, @@ -5328,12 +6006,20 @@ "set-iterator-seeds.ts": 3269, "set-optional-seeds.ts": 2164, "small-collection-transitions.ts": 3586, + "speculative-integer-globals.ts": 2999, + "speculative-integer-loops.ts": 4097, + "speculative-integer-reads.ts": 3960, "stack-depth-async-bodies.ts": 2727, "stack-overflow-async.ts": 2942, "stack-overflow-caught.ts": 3056, "stack-overflow-finally.ts": 3694, "stack-overflow-shapes.ts": 3565, "stack-overflow-uncaught.ts": 2532, + "stackless-async-concurrency.ts": 5553, + "stackless-async-context.ts": 3712, + "stackless-async-exceptions.ts": 4059, + "stackless-async-ordering.ts": 4230, + "stackless-async-unhandled.ts": 3409, "static-field-updates/main.ts": 2914, "static-method-receivers.ts": 2350, "stdio-values.mjs": 2761, @@ -5341,10 +6027,15 @@ "stdio-write-overrides.mjs": 3205, "stored-native-builtins.js": 3341, "stored-object-helpers.js": 2959, + "stream-dynamic-entry-kinds.cjs": 8165, + "stream-dynamic-entry-kinds.ts": 2619, + "stream-encoded-from-entries.cjs": 4394, + "stream-encoded-from-entries.ts": 4716, "string-char-code-integer-loops.ts": 2575, "string-code-points.ts": 2460, "string-concat-number-parts.ts": 2155, "string-construction-order.ts": 3307, + "string-field-append.ts": 3228, "string-lifetimes-boundaries.ts": 3304, "string-lifetimes-evaluation.ts": 2835, "string-lifetimes-iteration.ts": 2037, @@ -5371,6 +6062,7 @@ "text-codec-var.ts": 3480, "text-encode-into-boundaries.ts": 2224, "timers-clear-many.ts": 3073, + "truthy-object-logical.ts": 4450, "tuple-and-string-search.ts": 2595, "tuple-array-union.ts": 3305, "tuple-callback-visitation.ts": 3801, diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index 06f0e75a8..f1347bf23 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -3,7 +3,13 @@ import { globSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { expect, test } from "vitest"; -import { analyze, coverageEnvelope, coveragePasses, renderCoverage } from "@scriptc/compiler"; +import { + analyze, + coverageEnvelope, + coveragePasses, + renderCoverage, + renderExplainDynamic, +} from "@scriptc/compiler"; import { shardSelect, shardSuffix } from "./shard.js"; const repoRoot = join(import.meta.dirname, "../.."); @@ -37,6 +43,36 @@ 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. + await expect(report(fixture("wasm/main.ts"))).toMatchFileSnapshot( + "__snapshots__/coverage-wasm.txt", + ); + const { coverage } = analyze(fixture("wasm/main.ts"), { wasm: [fixture("wasm/add.wasm")] }); + const modules = coverage.wasmModules ?? []; + expect( + modules.map((m) => [ + m.path?.replace(repoRoot + "/", "") ?? "inline", + m.status, + m.unsupported, + m.sources.map((s) => s.kind), + ]), + ).toEqual([ + ["inline", "valid", [], ["inline"]], + ["tests/coverage-fixtures/wasm/add.wasm", "valid", [], ["path", "config"]], + [ + "tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm", + "invalid", + [], + ["package"], + ], + ["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]); +}); + test("divergences report as warnings, never as blockers", async () => { const { coverage } = analyze(fixture("divergences.ts")); expect(coverage.diagnostics).toEqual([]); @@ -230,6 +266,105 @@ test("CLI: build --print=diagnostics reports failures and divergence warnings as } }); +test("coverage counts the statements of dynamic-tier functions", async () => { + expect(analyze(join(repoRoot, "tests/corpus/400-fib.ts")).coverage.stats.statementsDynamic).toBe( + 0, + ); + const { coverage, sourceTexts } = analyze(fixture("tier-demotions.ts")); + expect(coverage.stats.statementsDynamic).toBe(1); + expect(coverage.tiers).toBeUndefined(); + expect(coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts }).stats).toMatchObject({ + statementsTotal: 5, + statementsStatic: 3, + statementsDynamic: 1, + statementsFailed: 2, + }); + await expect(report(fixture("tier-demotions.ts"))).toMatchFileSnapshot( + "__snapshots__/coverage-tier-demotions.txt", + ); +}); + +test("--explain-dynamic attributes each refusal to the unit the dynamic tier would take", async () => { + const { coverage, sourceTexts } = analyze(fixture("tier-demotions.ts"), { explainDynamic: true }); + const tiers = coverage.tiers!; + expect(tiers.mode).toBe("auto"); + expect(tiers.statementsDynamic).toBe(1); + expect(tiers.dynamicFunctions.map((fn) => fn.name)).toContain("readName"); + const requests = [...tiers.requests].sort((a, b) => a.loc.start - b.loc.start); + expect( + requests.map((r) => [r.code, r.seed, r.scope, r.unit.kind, r.unit.name, r.enclosing.name]), + ).toEqual([ + ["SC1090", "reflection", "reached", "class", "Point", ""], + ["SC2012", "operation", "reached", "binding", "n", "precise"], + ]); + expect(tiers.units.map((u) => [u.kind, u.name, u.requests, u.sites])).toEqual([ + ["class", "Point", 1, 0], + ["function", "", 0, 1], + ["function", "precise", 0, 1], + ["binding", "n", 1, 0], + ]); + const envelope = coverageEnvelope(coverage, { compilerVersion: "test", sourceTexts }); + expect(envelope.tiers?.requests[0]).toMatchObject({ unit: { kind: expect.any(String) } }); + const relative = JSON.parse(JSON.stringify(tiers).replaceAll(repoRoot + "/", "")) as typeof tiers; + await expect(renderExplainDynamic(relative)).toMatchFileSnapshot( + "__snapshots__/coverage-explain-dynamic.txt", + ); + expect(renderExplainDynamic(relative, { query: "Point" })).toContain("class Point"); + expect(renderExplainDynamic(relative, { query: "Point" })).not.toContain("binding n"); + expect(renderExplainDynamic(relative, { query: "nothing" })).toContain( + "no demoted unit matches 'nothing'", + ); +}); + +test("CLI: --tier and --explain-dynamic", () => { + const scriptcCli = join(repoRoot, "packages/cli/src/main.ts"); + const outDir = mkdtempSync(join(tmpdir(), "scriptc-tier-")); + const run = (...args: string[]) => + spawnSync(process.execPath, ["--import", "tsx", scriptcCli, ...args], { + cwd: repoRoot, + encoding: "utf8", + env: { ...process.env, SCRIPTC_NO_CACHE: "1" }, + }); + try { + const explained = run( + "coverage", + fixture("tier-demotions.ts"), + "--explain-dynamic", + "--tier=static-only", + ); + expect(explained.status).toBe(0); + expect(explained.stdout).toContain("in dynamic tier 1"); + expect(explained.stdout).toContain("--tier=static-only"); + expect(explained.stdout).toContain("binding n in precise"); + const one = run("coverage", fixture("tier-demotions.ts"), "--explain-dynamic=Point"); + expect(one.stdout).toContain("class Point"); + expect(one.stdout).not.toContain("binding n in precise"); + const bad = run("coverage", fixture("tier-demotions.ts"), "--tier=dynamic-only"); + expect(bad.status).toBe(1); + expect(bad.stderr).toContain('unknown --tier value "dynamic-only"'); + const misplaced = run("build", fixture("tier-demotions.ts"), "--explain-dynamic"); + expect(misplaced.status).toBe(1); + expect(misplaced.stderr).toContain("--explain-dynamic is a coverage option"); + // Until demotion lands, static-only compiles exactly like the default. + const ir = (name: string, ...args: string[]) => { + const out = join(outDir, name); + const built = run( + "build", + join(repoRoot, "tests/corpus/400-fib.ts"), + "--emit=ir", + "-o", + out, + ...args, + ); + expect(built.status).toBe(0); + return readFileSync(out, "utf8"); + }; + expect(ir("static-only.ir.json", "--tier=static-only")).toBe(ir("auto.ir.json")); + } finally { + rmSync(outDir, { recursive: true, force: true }); + } +}); + test("fully static program reports 100%", () => { const out = report(join(repoRoot, "tests/corpus/400-fib.ts")); expect(out).toContain("(100%)"); diff --git a/tests/harness/deadstrip.test.ts b/tests/harness/deadstrip.test.ts index 7db7ec404..1baa92ac9 100644 --- a/tests/harness/deadstrip.test.ts +++ b/tests/harness/deadstrip.test.ts @@ -1,5 +1,6 @@ /* Reachability's build contract — scriptc-only, deliberately NOT - * differential: + * differential (tests/corpus/6170-flight-dev-stacks-dead pins statement-level + * reachability differentially; here its emitted LLVM is grepped): * * - The dead-strip side (a program importing a module whose UNUSED exports * hold rejected constructs must build and run) is covered differentially @@ -49,6 +50,36 @@ test("unreached bodies leave no trace in the emitted LLVM", async () => { } }); +test("statements after an abrupt statement leave no trace and no stack frames", async () => { + const outDir = join(cacheDir, `deadstrip-abrupt${sanitize ? "-san" : ""}`); + const result = await compile(join(repoRoot, "tests/corpus/6170-flight-dev-stacks-dead.ts"), { + outPath: join(outDir, "program"), + outDir, + sanitize, + backend: "llvm", + }); + if (!result.ok) { + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + } + const c = readFileSync(result.llvmPath, "utf8"); + for (const reached of ["outlineComponentInfo", "emitOutlinedDebugModelChunk", "renderModel"]) { + expect(c, `expected reached '${reached}' in the emitted LLVM`).toContain(reached); + } + // The development-only stack parser is referenced only after the + // production stubs' throw: none of it is emitted, and the program keeps + // no source frames (its only stack read never runs). + for (const gone of [ + "parseStackTrace", + "collectStackTrace", + "getMethodCallName", + "renderDebugModel", + ]) { + expect(c, `unreached '${gone}' must leave no trace in the emitted LLVM`).not.toContain(gone); + } + expect(c).not.toContain("source_frame"); + expect(c).not.toContain("scr_stack_enter"); +}); + /** Compiles an in-repo fixture and returns its diagnostics codes+messages. * Deliberately NO backend pin: every program here fails during lowering, * before any backend runs — the default is irrelevant by construction, and diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index 28daa88d3..92ac8f5ab 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -928,6 +928,43 @@ hash.end("abc"); }); }); +// A Hash or Hmac held in an 'unknown' slot answers update(), digest() and +// Hash's copy(). Its other members throw a coded SC2020 error, and so do +// those methods called with another receiver. +describe("Hash and Hmac members through 'unknown'", () => { + test("throw SC2020 other than update, digest and copy", async () => { + const r = await compileAndRun( + "hash-unknown-members", + `const crypto = require("node:crypto"); +function attempt(value, run) { + try { + run(value); + } catch (error) { + console.log(error.name, error.code, error.message); + } +} +const hash = [crypto.createHash("sha256")][0]; +attempt(hash, (h) => h.on("data", () => {})); +attempt(hash, (h) => h.readable); +attempt([crypto.createHmac("sha256", "k")][0], (m) => m.write("x")); +attempt(hash, (h) => h.digest.call({})); +console.log(hash.update("abc").digest("hex").slice(0, 8)); +`, + "cjs", + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + "Error SC2020 Hash members other than update(), digest() and copy() have no lowering on an 'unknown' value; cast the value to Hash first\n".repeat( + 2, + ) + + "Error SC2020 Hmac members other than update() and digest() have no lowering on an 'unknown' value; cast the value to Hmac first\n" + + "Error SC2020 Hash and Hmac methods called with a receiver other than their own Hash or Hmac have no lowering\n" + + "ba7816bf\n", + ); + expect(r.stderr).toBe(""); + }); +}); + // Node 24.22 skips the chacha20-poly1305 tag check when neither // setAuthTag() nor authTagLength was used, and returns unverified // plaintext. scriptc keeps Node 24.15's behavior: final() fails. @@ -960,3 +997,136 @@ for (const input of [ciphertext, Buffer.from("forged data"), Buffer.alloc(0)]) { expect(r.stderr).toBe(""); }); }); + +// Function expressions the frontend lowers speculatively (the receiver of +// `.call`/`.apply`, a computed class base) keep their refused statements as +// runtime fences. They used to vanish: the capture buffer held the refusal, +// the candidate was kept, and the statement was simply missing from it. +describe("refused statements in speculatively lowered JavaScript functions", () => { + test.each([ + [ + "speculative-call-this", + `(function () { + var root = this; + console.log("before"); + console.log(root === eval("this")); + console.log("after"); +}).call(this); +`, + "before\n", + 4, + ], + [ + "speculative-apply", + `(function () { + console.log("before"); + console.log(typeof eval("1")); +}).apply(null, []); +`, + "before\n", + 3, + ], + [ + "speculative-class-base", + `class Base {} +class Derived extends (function () { + console.log("before"); + console.log(typeof eval("1")); + return Base; +})() {} +console.log(typeof Derived); +`, + "before\n", + 4, + ], + ] as const)("%s", async (name, source, stdout, line) => { + const r = await compileAndRun(name, source, "cjs"); + expect(r.exitCode).toBe(1); + 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$`, + ), + ); + }); +}); + +// CommonJS forms around the module's exports that keep a refusal name the +// construct itself (they used to report an unlowerable `require` value, a +// "binding form with no lowering", or the ES-module `module` message). +describe("CommonJS export-object and inline require refusals", () => { + test.each([ + [ + "exports-as-argument", + `exports.x = 1; +function keys(o) { + return Object.keys(o); +} +console.log("before"); +console.log(keys(exports)); +`, + {}, + "before\n", + 6, + "'exports' as a value other than a member access, comparison, typeof or truthiness test \\(the compiled exports are a static table, not an object: read exports\\.\\)", + ], + [ + "exports-rebinding", + `console.log("before"); +exports = module.exports = function hash(x) { + return x; +}; +exports.extra = 1; +`, + {}, + "before\n", + 2, + "rebinding 'exports' \\(exports = module\\.exports = value\\)", + ], + [ + "require-computed", + `const name = "./lib.cjs"; +console.log("before"); +console.log(require(name).x); +`, + { "lib.cjs": "exports.x = 1;\n" }, + "before\n", + 3, + "require\\(\\) with a computed specifier", + ], + [ + "exports-call-reads-this", + `exports.timeouts = function (n) { + return this.scale * n; +}; +exports.scale = 2; +console.log("before"); +console.log(exports.timeouts(3)); +`, + {}, + "before\n", + 6, + "calling 'exports\\.timeouts' through the module's exports object when the function may read 'this'", + ], + [ + "require-table-as-value", + `function count(o) { + return Object.keys(o).length; +} +console.log("before"); +console.log(count(require("./lib.cjs"))); +`, + { "lib.cjs": "exports.x = 1;\n" }, + "before\n", + 5, + "the exports object of '\\./lib\\.cjs' as a value", + ], + ] as const)("%s", async (name, source, extra, stdout, line, message) => { + const r = await compileAndRun(name, source, "cjs", extra); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe(stdout); + expect(r.stderr).toMatch( + new RegExp(`^Uncaught Error: ${message}.*\\[SC1090 at .*${name}\\.cjs:${line}\\]\\n$`), + ); + }); +}); diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index c5021fd06..356d2fa6d 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -69,7 +69,7 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise const hash = createHash("sha256"); const inputs = [ entry, - ...globSync(join(pilotRoot, "**/node_modules/**/*.{js,mjs,cjs,json,d.ts}")).sort(), + ...globSync(join(pilotRoot, "**/node_modules/**/*.{js,mjs,cjs,json,ts}")).sort(), ...globSync( join(fixturesRoot, "commander-calc/node_modules/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), @@ -821,10 +821,71 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { 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 --npm-static=ms... to include its dependencies)", }, ]); }, 120_000); + // 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")); + expect(coverage.npmStatic).toEqual([ + { + package: "twinned", + 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)", + }, + ]); + const diagnostic = coverage.diagnostics.find((d) => d.code === "SC2013"); + expect(diagnostic?.message).toContain("'twinned'"); + expect(diagnostic?.hint).toContain("--npm-static=twinned"); + }, 120_000); + + test("a named package's untyped dependency points at the transitive opt-in", () => { + 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", + }); + }, 120_000); + + test("--npm-static=pkg... compiles the package's dependency closure", async () => { + const entry = join(pilotRoot, "twinned-cli.ts"); + const { coverage } = analyze(entry, { npmStatic: ["twinned..."] }); + expect(coverage.npmStatic).toEqual([ + { package: "twinned", status: "static" }, + { package: "ms", status: "static" }, + ]); + expect(coverage.preflightFailed).toBe(false); + const binary = await buildStatic(entry, ["twinned..."]); + 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("type-only imports select no package", async () => { + const entry = join(pilotRoot, "type-only-cli.ts"); + const { coverage } = analyze(entry); + expect(coverage.npmStatic ?? []).toEqual([]); + expect(coverage.preflightFailed).toBe(false); + expect(coverage.diagnostics).toEqual([]); + const binary = await buildStatic(entry, []); + 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("ms supports invalid duration strings without runtime fences", () => { const { coverage } = analyze(join(pilotRoot, "ms-invalid-cli.ts"), { npmStatic: ["ms"] }); expect(coverage.npmStatic).toEqual([{ package: "ms", status: "static" }]); @@ -974,6 +1035,7 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { package: "typegapped", status: "refused", detail: "it ships no own .d.ts declaration surface", + hint: "to compile it anyway, name it with --npm-static=typegapped (or --npm-static=typegapped... to include its dependencies)", }, { package: "escape-string-regexp", status: "static" }, ]); @@ -999,6 +1061,40 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { expect(nativeRes.exitCode).toBe(nodeRes.exitCode); }, 180_000); + // The two resolvers must answer one file for every opted-in edge. Each + // shape below once left the runtime file outside tsgo's program (SC2013 + // "did not join the type-checked program"): uuid's node condition with + // declarations in another tree, tldts's package-root TypeScript source + // beside `main`, express's same-version copies of one package (tsgo's + // package-id redirect), and get-intrinsic's dependency chain deeper than + // maxNodeModuleJsDepth. + test.each([ + ["split-node-types-cli.ts", ["split-node-types"]], + ["ts-root-source-cli.ts", ["ts-root-source"]], + ["dup-install-cli.ts", ["dup-left", "dup-right", "dup-leaf"]], + ["deep-packages-cli.ts", Array.from({ length: 11 }, (_, i) => `hop-${i + 1}`)], + ])( + "%s joins the type-checked program and matches Node", + async (name, pkgs) => { + const entry = join(pilotRoot, name); + const { coverage } = analyze(entry, { npmStatic: pkgs }); + expect(coverage.npmStatic.map((p) => [p.package, p.status]).sort()).toEqual( + pkgs.map((p) => [p, "static"]).sort(), + ); + expect(coverage.preflightFailed).toBe(false); + expect(coverage.diagnostics).toHaveLength(0); + const binary = await buildStatic(entry, pkgs); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + expect(nativeRes.stdout).toEqual(nodeRes.stdout); + expect(comparableStderr(nativeRes.stderr)).toEqual(nodeRes.stderr); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, + 180_000, + ); + test("recursive inferred JavaScript results retain their promised ABI", async () => { const entry = join(pilotRoot, "recursive-result-cli.ts"); const binary = await buildStatic(entry, ["recursive-result"]); diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index a6cdee2b5..b6af0dbb6 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -43,15 +43,15 @@ const KNOWN: Record = { "cjs-lexer-invisible": { build: "SC2013" }, "commander-calc": { run: "SC1090", argvs: [["--help"]] }, "commander-calc-async": { run: "SC1090", argvs: [["--help"]] }, - "create-require": { run: "SC2009" }, - "dotfolder": { run: "SC2009" }, + "create-require": { run: "SC1090" }, + "dotfolder": { run: "SC1090" }, "dual-package-kinds": { build: "SC2013" }, "dynamic-import": { build: "SC2012" }, "dynamic-import-local": { build: "SC2012" }, "esbuild-require": { build: "SC2013" }, "esm-missing-default": { build: "SC1090" }, "esm-missing-export": { build: "SC1090" }, - "fetch-destructure": { build: "SC1090" }, + "fetch-destructure": { run: "SC1031" }, "fs-shims": { run: "SC2020" }, "hoisted-workspace": { build: "SC2013" }, "import-refusal": { build: "SC2020" }, @@ -65,7 +65,7 @@ const KNOWN: Record = { "misc-shims": { build: "SC2013" }, "namespace": { build: "SC1013" }, "node24-async-events": { run: "SC2020" }, - "node24-commonjs-metadata": { run: "SC1090" }, + "node24-commonjs-metadata": { run: "SC2020" }, "node24-crypto-kdf": { run: "SC2020" }, "node24-deep-equality": { run: "SC2020" }, "node24-diagnostics-async": { run: "SC1090" }, @@ -109,7 +109,6 @@ const KNOWN: Record = { "web-streams": { build: "SC2013" }, "workspace-copied": { build: "SC2013" }, "workspace-unclean": { build: "SC2013" }, - "zlib-shims": { run: "SC2020" }, }; const cases = shardSelect(npmCases(fixturesRoot), (c) => c.name); diff --git a/tests/harness/self-hosting-range-cases.ts b/tests/harness/self-hosting-range-cases.ts index 05d6a1fe6..ed2b24ca9 100644 --- a/tests/harness/self-hosting-range-cases.ts +++ b/tests/harness/self-hosting-range-cases.ts @@ -375,7 +375,8 @@ export function integerRangeCases(): RangeCase[] { ], }, ], - [proof(cursor, 0, 80), proof(decoded, 0, 4294967295), proof(head, 0, 124)], + // Passes enter at 0, 24, 48, 72 and 96 (at most 99), so the failing test reads at most 123. + [proof(cursor, 0, 80), proof(decoded, 0, 4294967295), proof(head, 0, 123)], (func) => { for (const id of ["cursor", "decoded"]) func.locals.push({ id, name: id, type: F64, mutable: true }); diff --git a/tests/harness/zero-cost-gate.test.ts b/tests/harness/zero-cost-gate.test.ts new file mode 100644 index 000000000..0b08da748 --- /dev/null +++ b/tests/harness/zero-cost-gate.test.ts @@ -0,0 +1,160 @@ +import { spawnSync } from "node:child_process"; +import { expect, test } from "vitest"; +import { + cachegrindCounts, + compareArtifact, + compareCounts, + compareExecutables, + dynamicTierSymbols, + nmOutput, + programVerdict, +} from "../../scripts/zero-cost-compare.mjs"; + +const ir = (functions: object[], globals: object[]) => + Buffer.from(JSON.stringify({ irVersion: 16, functions, globals, sourceFile: "/p/main.ts" })); + +const llvm = (body: string, extra = "") => + Buffer.from( + [ + "; Generated by scriptc (LLVM backend) from /p/main.ts. Do not edit.", + '@.str = private constant [3 x i8] c"hi\\00"', + "define internal i64 @add(i64 %a, i64 %b) {", + ` ${body}`, + "}", + 'define void @"main.$1"() { ret void }', + extra, + "declare void @scr_print(ptr)", + "", + ].join("\n"), + ); + +test("the zero-cost gate names the first differing IR and LLVM unit", () => { + const f = { name: "f", params: [], body: [{ kind: "return" }] }; + const g = { name: "g", params: [], body: [] }; + const base = ir([f, g, f], [{ name: "a", type: "i64" }]); + expect(compareArtifact("ir", base, Buffer.from(base))).toEqual({ identical: true }); + + const changed = compareArtifact( + "ir", + base, + ir([f, { ...g, params: [1] }, f], [{ name: "a", type: "i64" }]), + ); + expect(changed).toMatchObject({ + identical: false, + first: "changed g", + changed: ["g"], + added: [], + module: null, + }); + + const header = compareArtifact("ir", base, ir([f, g, f], [{ name: "a", type: "f64" }])); + expect(header).toMatchObject({ first: "module globals[0:a]", changed: [] }); + // Repeated names are matched by occurrence, so an insertion shows as added. + expect(compareArtifact("ir", base, ir([f, g, f, f], [{ name: "a", type: "i64" }]))).toMatchObject( + { + first: "added f#3", + }, + ); + + const ll = llvm("%r = add i64 %a, %b\n ret i64 %r"); + expect(compareArtifact("llvm", ll, Buffer.from(ll))).toEqual({ identical: true }); + expect(compareArtifact("llvm", ll, llvm("%r = sub i64 %a, %b\n ret i64 %r"))).toMatchObject({ + first: "changed add", + changed: ["add"], + functions: { baseline: 2, candidate: 2 }, + }); + expect( + compareArtifact( + "llvm", + ll, + llvm("%r = add i64 %a, %b\n ret i64 %r", "define void @new() { ret void }"), + ), + ).toMatchObject({ first: "added new", added: ["new"] }); + const global = compareArtifact("llvm", ll, Buffer.from(ll.toString().replace("hi", "ho"))); + expect(global).toMatchObject({ first: "module line 2", changed: [] }); + expect(global.module.candidate).toContain("ho"); +}); + +test("the zero-cost gate compares executables and instruction counts per function", () => { + const nm = + "0000000000001000 0000000000000010 T main\n0000000000001010 0000000000000020 t scr_add\n"; + const resized = compareExecutables( + Buffer.from("abcd"), + Buffer.from("abXd"), + nm, + nm.replace("0000000000000020 t", "0000000000000030 t"), + ); + expect(resized).toMatchObject({ identical: false, first: "resized scr_add", offset: 2 }); + + const output = (main: number, prefault: number, loader: number) => + [ + "events: Ir", + "fl=???", + "fn=main", + `0 ${main}`, + "fn=scr_sa_prefault", + `0 ${prefault}`, + "fn=_dl_lookup_symbol_x", + `0 ${loader}`, + "fn=scr_add", + "0 7", + "0 3", + ].join("\n"); + const own = new Set(["main", "scr_add", "scr_sa_prefault"]); + const base = cachegrindCounts(output(100, 50, 9000), own); + expect([...base]).toEqual([ + ["main", 100], + ["scr_add", 10], + ]); + expect(compareCounts(base, cachegrindCounts(output(100, 75, 9300), own)).identical).toBe(true); + expect(compareCounts(base, cachegrindCounts(output(104, 50, 9000), own))).toMatchObject({ + identical: false, + total: { baseline: 110, candidate: 114 }, + deltas: [{ name: "main", delta: 4 }], + }); +}); + +test("the zero-cost gate throws when nm does not list a binary's symbols", () => { + const ok = { status: 0, signal: null, stdout: "0000000000001000 T main\n", stderr: "" }; + expect(nmOutput("prog", ok)).toBe(ok.stdout); + const missing = spawnSync("scriptc-missing-nm", ["prog"], { encoding: "utf8" }); + expect(() => nmOutput("prog", missing)).toThrow(/^nm prog: .*ENOENT/); + expect(() => + nmOutput("prog", { status: null, signal: "SIGKILL", stdout: "", stderr: "" }), + ).toThrow("nm prog: killed by SIGKILL"); + expect(() => + nmOutput("prog", { status: 1, signal: null, stdout: "", stderr: "nm: prog: no symbols\n" }), + ).toThrow("nm prog: exited with status 1: nm: prog: no symbols"); +}); + +test("the zero-cost gate recognizes dynamic-tier runtime symbols", () => { + expect( + dynamicTierSymbols([ + "main", + "scr_dyn_release", + "scr_dyn_obj_drop_keys", + "scr_dyn_this_push_dyn", + "scr_js_load_ic", + "scr_dyn_obj_read", + "scr_dyn_obj_read", + ]), + ).toEqual(["scr_dyn_obj_read", "scr_js_load_ic"]); +}); + +test("the zero-cost gate fails differing programs unless they match the signature", () => { + const ok = { ok: true }; + const same = { ir: { identical: true }, exe: { identical: true } }; + const differs = { ir: { identical: true }, exe: { identical: false } }; + const verdict = ( + artifacts: object, + allowed = false, + baseline: object = ok, + candidate: object = ok, + ) => programVerdict({ baseline, candidate, artifacts, allowed }); + expect(verdict(same)).toEqual({ verdict: "identical", fails: false }); + expect(verdict(differs)).toEqual({ verdict: "differs", fails: true }); + expect(verdict(differs, true)).toEqual({ verdict: "allowed", fails: false }); + const failed = { ok: false, error: "SC1090" }; + expect(verdict({}, false, failed, failed)).toEqual({ verdict: "both-fail", fails: false }); + expect(verdict({}, true, ok, failed)).toEqual({ verdict: "build-error", fails: true }); +});